1<?php 2 3/* 4 * This file is part of the Symfony package. 5 * 6 * (c) Fabien Potencier <fabien@symfony.com> 7 * 8 * For the full copyright and license information, please view the LICENSE 9 * file that was distributed with this source code. 10 */ 11 12namespace Symfony\Component\Process\Pipes; 13 14use Symfony\Component\Process\Exception\InvalidArgumentException; 15 16/** 17 * @author Romain Neutron <imprec@gmail.com> 18 * 19 * @internal 20 */ 21abstract class AbstractPipes implements PipesInterface 22{ 23 public $pipes = []; 24 25 private $inputBuffer = ''; 26 private $input; 27 private $blocked = true; 28 private $lastError; 29 30 /** 31 * @param resource|string|int|float|bool|\Iterator|null $input 32 */ 33 public function __construct($input) 34 { 35 if (\is_resource($input) || $input instanceof \Iterator) { 36 $this->input = $input; 37 } elseif (\is_string($input)) { 38 $this->inputBuffer = $input; 39 } else { 40 $this->inputBuffer = (string) $input; 41 } 42 } 43 44 /** 45 * {@inheritdoc} 46 */ 47 public function close() 48 { 49 foreach ($this->pipes as $pipe) { 50 if (\is_resource($pipe)) { 51 fclose($pipe); 52 } 53 } 54 $this->pipes = []; 55 } 56 57 /** 58 * Returns true if a system call has been interrupted. 59 */ 60 protected function hasSystemCallBeenInterrupted(): bool 61 { 62 $lastError = $this->lastError; 63 $this->lastError = null; 64 65 // stream_select returns false when the `select` system call is interrupted by an incoming signal 66 return null !== $lastError && false !== stripos($lastError, 'interrupted system call'); 67 } 68 69 /** 70 * Unblocks streams. 71 */ 72 protected function unblock() 73 { 74 if (!$this->blocked) { 75 return; 76 } 77 78 foreach ($this->pipes as $pipe) { 79 stream_set_blocking($pipe, 0); 80 } 81 if (\is_resource($this->input)) { 82 stream_set_blocking($this->input, 0); 83 } 84 85 $this->blocked = false; 86 } 87 88 /** 89 * Writes input to stdin. 90 * 91 * @throws InvalidArgumentException When an input iterator yields a non supported value 92 */ 93 protected function write(): ?array 94 { 95 if (!isset($this->pipes[0])) { 96 return null; 97 } 98 $input = $this->input; 99 100 if ($input instanceof \Iterator) { 101 if (!$input->valid()) { 102 $input = null; 103 } elseif (\is_resource($input = $input->current())) { 104 stream_set_blocking($input, 0); 105 } elseif (!isset($this->inputBuffer[0])) { 106 if (!\is_string($input)) { 107 if (!is_scalar($input)) { 108 throw new InvalidArgumentException(sprintf('"%s" yielded a value of type "%s", but only scalars and stream resources are supported.', get_debug_type($this->input), get_debug_type($input))); 109 } 110 $input = (string) $input; 111 } 112 $this->inputBuffer = $input; 113 $this->input->next(); 114 $input = null; 115 } else { 116 $input = null; 117 } 118 } 119 120 $r = $e = []; 121 $w = [$this->pipes[0]]; 122 123 // let's have a look if something changed in streams 124 if (false === @stream_select($r, $w, $e, 0, 0)) { 125 return null; 126 } 127 128 foreach ($w as $stdin) { 129 if (isset($this->inputBuffer[0])) { 130 $written = fwrite($stdin, $this->inputBuffer); 131 $this->inputBuffer = substr($this->inputBuffer, $written); 132 if (isset($this->inputBuffer[0])) { 133 return [$this->pipes[0]]; 134 } 135 } 136 137 if ($input) { 138 while (true) { 139 $data = fread($input, self::CHUNK_SIZE); 140 if (!isset($data[0])) { 141 break; 142 } 143 $written = fwrite($stdin, $data); 144 $data = substr($data, $written); 145 if (isset($data[0])) { 146 $this->inputBuffer = $data; 147 148 return [$this->pipes[0]]; 149 } 150 } 151 if (feof($input)) { 152 if ($this->input instanceof \Iterator) { 153 $this->input->next(); 154 } else { 155 $this->input = null; 156 } 157 } 158 } 159 } 160 161 // no input to read on resource, buffer is empty 162 if (!isset($this->inputBuffer[0]) && !($this->input instanceof \Iterator ? $this->input->valid() : $this->input)) { 163 $this->input = null; 164 fclose($this->pipes[0]); 165 unset($this->pipes[0]); 166 } elseif (!$w) { 167 return [$this->pipes[0]]; 168 } 169 170 return null; 171 } 172 173 /** 174 * @internal 175 */ 176 public function handleError(int $type, string $msg) 177 { 178 $this->lastError = $msg; 179 } 180} 181