Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 117 additions & 0 deletions src/Symfony/Component/Console/Output/CombinedOutput.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
<?php

/*
* This file is part of the Fidry\Console package.
*
* (c) Théo FIDRY <theo.fidry@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace Symfony\Component\Console\Output;

use DomainException;
use Symfony\Component\Console\Formatter\OutputFormatterInterface;
use function count;
use function func_get_args;

/**
* @internal
*/
final class CombinedOutput implements OutputInterface
{
/**
* @var OutputInterface[]
*/
private array $outputs;

public function __construct(
OutputInterface ...$outputs,
) {
self::assertHasAtLeastOneOutput($outputs);

$this->outputs = $outputs;
}

public function write(iterable|string $messages, bool $newline = false, int $options = 0): void
{
foreach ($this->outputs as $output) {
$output->write(...func_get_args());
}
}

public function writeln(iterable|string $messages, int $options = 0): void
{
foreach ($this->outputs as $output) {
$output->writeln(...func_get_args());
}
}

public function setVerbosity(int $level): void
{
throw new DomainException('Should not called.');
}

public function getVerbosity(): int
{
throw new DomainException('Should not called.');
}

public function isSilent(): bool
{
throw new DomainException('Should not called.');
}

public function isQuiet(): bool
{
throw new DomainException('Should not called.');
}

public function isVerbose(): bool
{
throw new DomainException('Should not called.');
}

public function isVeryVerbose(): bool
{
throw new DomainException('Should not called.');
}

public function isDebug(): bool
{
throw new DomainException('Should not called.');
}

public function setDecorated(bool $decorated): void
{
throw new DomainException('Should not called.');
}

public function isDecorated(): bool
{
throw new DomainException('Should not called.');
}

public function setFormatter(OutputFormatterInterface $formatter): void
{
throw new DomainException('Should not called.');
}

public function getFormatter(): OutputFormatterInterface
{
throw new DomainException('Should not called.');
}

/**
* @param OutputInterface[] $outputs
*/
private static function assertHasAtLeastOneOutput(array $outputs): void
{
if (count($outputs) < 1) {
throw new DomainException('Expected at least one output.');
}
}
}
3 changes: 3 additions & 0 deletions src/Symfony/Component/Console/Output/StreamOutput.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@

use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Formatter\OutputFormatterInterface;
use function rewind;
use function stream_get_contents;

/**
* StreamOutput writes the output to a given stream.
Expand Down Expand Up @@ -74,6 +76,7 @@ protected function doWrite(string $message, bool $newline): void
fflush($this->stream);
}

// TODO: should be public & static?
/**
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this piece of code is implemented and use in several places. I wonder if it would make sense to either expose it as a static public utility or extract it and re-use that extracted utility here.

But irrelevant to the MR, to clean up

* Returns true if the stream supports colorization.
*
Expand Down
177 changes: 177 additions & 0 deletions src/Symfony/Component/Console/Output/TestOutput.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
<?php

/*
* This file is part of the Fidry\Console package.
*
* (c) Théo FIDRY <theo.fidry@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace Symfony\Component\Console\Output;

use DomainException;
use RuntimeException;
use Symfony\Component\Console\Formatter\OutputFormatterInterface;
use function fopen;
use function func_get_args;
use function rewind;
use function stream_get_contents;

/**
* @internal
*
* @author Théo FIDRY <theo.fidry@gmail.com>
*/
final class TestOutput implements ConsoleOutputInterface
{
private OutputInterface $innerOutput;
private OutputInterface $innerErrorOutput;
private OutputInterface $displayOutput;
private CombinedOutput $output;
private CombinedOutput $errorOutput;

/**
* @param OutputInterface::VERBOSITY_* $verbosity
*/
public function __construct(
private int $verbosity,
private OutputFormatterInterface $formatter,
) {
$this->innerOutput = self::createOutput($this);
$this->innerErrorOutput = self::createOutput($this);
$this->displayOutput = self::createOutput($this);

$this->output = new CombinedOutput(
$this->innerOutput,
$this->displayOutput,
);
$this->errorOutput = new CombinedOutput(
$this->innerErrorOutput,
$this->displayOutput,
);
}

public function getOutputContents(): string
{
return $this->getStreamContents($this->innerOutput);
}

public function getErrorOutputContents(): string
{
return $this->getStreamContents($this->innerErrorOutput);
}

public function getDisplayContents(): string
{
return $this->getStreamContents($this->displayOutput);
}

public function getErrorOutput(): OutputInterface
{
return $this->errorOutput;
}

public function setErrorOutput(OutputInterface $error): void
{
throw new DomainException('Should not be modified.');
}

public function section(): ConsoleSectionOutput
{
throw new DomainException('Not supported (yet).');
}

public function write(iterable|string $messages, bool $newline = false, int $options = 0): void
{
$this->output->write(...func_get_args());
}

public function writeln(iterable|string $messages, int $options = 0): void
{
$this->output->writeln(...func_get_args());
}

public function setVerbosity(int $level): void
{
throw new DomainException('Should not be modified.');
}

public function getVerbosity(): int
{
return $this->verbosity;
}

public function isSilent(): bool
{
return self::VERBOSITY_SILENT <= $this->verbosity;
}

public function isQuiet(): bool
{
return self::VERBOSITY_QUIET === $this->verbosity;
}

public function isVerbose(): bool
{
return self::VERBOSITY_VERBOSE <= $this->verbosity;
}

public function isVeryVerbose(): bool
{
return self::VERBOSITY_VERY_VERBOSE <= $this->verbosity;
}

public function isDebug(): bool
{
return self::VERBOSITY_DEBUG <= $this->verbosity;
}

public function setDecorated(bool $decorated): void
{
throw new DomainException('Should not be modified.');
}

public function isDecorated(): bool
{
return $this->formatter->isDecorated();
}

public function setFormatter(OutputFormatterInterface $formatter): void
{
throw new DomainException('Should not be modified.');
}

public function getFormatter(): OutputFormatterInterface
{
return $this->formatter;
}

private static function createOutput(OutputInterface $config): StreamOutput
{
$stream = fopen('php://memory', 'wb');

if (false === $stream) {
throw new RuntimeException('Failed to open stream.');
}

return new StreamOutput(
$stream,
$config->getVerbosity(),
$config->isDecorated(),
$config->getFormatter(),
);
}

private function getStreamContents(StreamOutput $output): string
{
$stream = $output->getStream();

rewind($stream);

return stream_get_contents($stream);

Check failure on line 175 in src/Symfony/Component/Console/Output/TestOutput.php

View workflow job for this annotation

GitHub Actions / Psalm

FalsableReturnStatement

src/Symfony/Component/Console/Output/TestOutput.php:175:16: FalsableReturnStatement: The declared return type 'string' for Symfony\Component\Console\Output\TestOutput::getStreamContents does not allow false, but the function returns 'false|string' (see https://psalm.dev/137)

Check failure on line 175 in src/Symfony/Component/Console/Output/TestOutput.php

View workflow job for this annotation

GitHub Actions / Psalm

FalsableReturnStatement

src/Symfony/Component/Console/Output/TestOutput.php:175:16: FalsableReturnStatement: The declared return type 'string' for Symfony\Component\Console\Output\TestOutput::getStreamContents does not allow false, but the function returns 'false|string' (see https://psalm.dev/137)
}
}
Loading
Loading