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;
13
14use Symfony\Component\Process\Exception\RuntimeException;
15
16/**
17 * Provides a way to continuously write to the input of a Process until the InputStream is closed.
18 *
19 * @author Nicolas Grekas <p@tchwork.com>
20 *
21 * @implements \IteratorAggregate<int, string>
22 */
23class InputStream implements \IteratorAggregate
24{
25    /** @var callable|null */
26    private $onEmpty = null;
27    private $input = [];
28    private $open = true;
29
30    /**
31     * Sets a callback that is called when the write buffer becomes empty.
32     */
33    public function onEmpty(callable $onEmpty = null)
34    {
35        $this->onEmpty = $onEmpty;
36    }
37
38    /**
39     * Appends an input to the write buffer.
40     *
41     * @param resource|string|int|float|bool|\Traversable|null $input The input to append as scalar,
42     *                                                                stream resource or \Traversable
43     */
44    public function write($input)
45    {
46        if (null === $input) {
47            return;
48        }
49        if ($this->isClosed()) {
50            throw new RuntimeException(sprintf('"%s" is closed.', static::class));
51        }
52        $this->input[] = ProcessUtils::validateInput(__METHOD__, $input);
53    }
54
55    /**
56     * Closes the write buffer.
57     */
58    public function close()
59    {
60        $this->open = false;
61    }
62
63    /**
64     * Tells whether the write buffer is closed or not.
65     */
66    public function isClosed()
67    {
68        return !$this->open;
69    }
70
71    /**
72     * @return \Traversable<int, string>
73     */
74    #[\ReturnTypeWillChange]
75    public function getIterator()
76    {
77        $this->open = true;
78
79        while ($this->open || $this->input) {
80            if (!$this->input) {
81                yield '';
82                continue;
83            }
84            $current = array_shift($this->input);
85
86            if ($current instanceof \Iterator) {
87                yield from $current;
88            } else {
89                yield $current;
90            }
91            if (!$this->input && $this->open && null !== $onEmpty = $this->onEmpty) {
92                $this->write($onEmpty($this));
93            }
94        }
95    }
96}
97