mirror of https://github.com/FriendsOfTYPO3/tea.git synced 2024-11-26 19:56:13 +01:00
tea/tools/typo3-typoscript-lint
Oliver Klee ef38d7d84b
[TASK] Drop the .phar suffix from the tools (#204)
PhpStorm by default indexes `*.phar` files. For our current set of
tools, we do not want this. (This keeps PhpStorm from complaining
about multiple versions of the same class.)

Also mark the tools as binary for git.
2021-02-24 12:02:56 +01:00

82760 lines
No EOL
2.4 MiB
Executable file
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env php
<?php
/**
* Bundled by phar-composer with the help of php-box.
*
* @link https://github.com/clue/phar-composer
*/
define('BOX_EXTRACT_PATTERN_DEFAULT', '__HALT' . '_COMPILER(); ?>');
define('BOX_EXTRACT_PATTERN_OPEN', "__HALT" . "_COMPILER(); ?>\r\n");
if (class_exists('Phar')) {
Phar::mapPhar('');
require 'phar://' . __FILE__ . '/typoscript-lint';
} else {
$extract = new Extract(__FILE__, Extract::findStubLength(__FILE__));
$dir = $extract->go();
set_include_path($dir . PATH_SEPARATOR . get_include_path());
require "$dir/typoscript-lint";
}
class Extract
{
const PATTERN_DEFAULT = BOX_EXTRACT_PATTERN_DEFAULT;
const PATTERN_OPEN = BOX_EXTRACT_PATTERN_OPEN;
const GZ = 0x1000;
const BZ2 = 0x2000;
const MASK = 0x3000;
private $file;
private $handle;
private $stub;
public function __construct($file, $stub)
{
if (!is_file($file)) {
throw new InvalidArgumentException(
sprintf(
'The path "%s" is not a file or does not exist.',
$file
)
);
}
$this->file = $file;
$this->stub = $stub;
}
public static function findStubLength(
$file,
$pattern = self::PATTERN_OPEN
) {
if (!($fp = fopen($file, 'rb'))) {
throw new RuntimeException(
sprintf(
'The phar "%s" could not be opened for reading.',
$file
)
);
}
$stub = null;
$offset = 0;
$combo = str_split($pattern);
while (!feof($fp)) {
if (fgetc($fp) === $combo[$offset]) {
$offset++;
if (!isset($combo[$offset])) {
$stub = ftell($fp);
break;
}
} else {
$offset = 0;
}
}
fclose($fp);
if (null === $stub) {
throw new InvalidArgumentException(
sprintf(
'The pattern could not be found in "%s".',
$file
)
);
}
return $stub;
}
public function go($dir = null)
{
if (null === $dir) {
$dir = rtrim(sys_get_temp_dir(), '\\/')
. DIRECTORY_SEPARATOR
. 'pharextract'
. DIRECTORY_SEPARATOR
. basename($this->file, '.phar');
} else {
$dir = realpath($dir);
}
$md5 = $dir . DIRECTORY_SEPARATOR . md5_file($this->file);
if (file_exists($md5)) {
return $dir;
}
if (!is_dir($dir)) {
$this->createDir($dir);
}
$this->open();
if (-1 === fseek($this->handle, $this->stub)) {
throw new RuntimeException(
sprintf(
'Could not seek to %d in the file "%s".',
$this->stub,
$this->file
)
);
}
$info = $this->readManifest();
if ($info['flags'] & self::GZ) {
if (!function_exists('gzinflate')) {
throw new RuntimeException(
'The zlib extension is (gzinflate()) is required for "%s.',
$this->file
);
}
}
if ($info['flags'] & self::BZ2) {
if (!function_exists('bzdecompress')) {
throw new RuntimeException(
'The bzip2 extension (bzdecompress()) is required for "%s".',
$this->file
);
}
}
self::purge($dir);
$this->createDir($dir);
$this->createFile($md5);
foreach ($info['files'] as $info) {
$path = $dir . DIRECTORY_SEPARATOR . $info['path'];
$parent = dirname($path);
if (!is_dir($parent)) {
$this->createDir($parent);
}
if (preg_match('{/$}', $info['path'])) {
$this->createDir($path, 0777, false);
} else {
$this->createFile(
$path,
$this->extractFile($info)
);
}
}
return $dir;
}
public static function purge($path)
{
if (is_dir($path)) {
foreach (scandir($path) as $item) {
if (('.' === $item) || ('..' === $item)) {
continue;
}
self::purge($path . DIRECTORY_SEPARATOR . $item);
}
if (!rmdir($path)) {
throw new RuntimeException(
sprintf(
'The directory "%s" could not be deleted.',
$path
)
);
}
} else {
if (!unlink($path)) {
throw new RuntimeException(
sprintf(
'The file "%s" could not be deleted.',
$path
)
);
}
}
}
private function createDir($path, $chmod = 0777, $recursive = true)
{
if (!mkdir($path, $chmod, $recursive)) {
throw new RuntimeException(
sprintf(
'The directory path "%s" could not be created.',
$path
)
);
}
}
private function createFile($path, $contents = '', $mode = 0666)
{
if (false === file_put_contents($path, $contents)) {
throw new RuntimeException(
sprintf(
'The file "%s" could not be written.',
$path
)
);
}
if (!chmod($path, $mode)) {
throw new RuntimeException(
sprintf(
'The file "%s" could not be chmodded to %o.',
$path,
$mode
)
);
}
}
private function extractFile($info)
{
if (0 === $info['size']) {
return '';
}
$data = $this->read($info['compressed_size']);
if ($info['flags'] & self::GZ) {
if (false === ($data = gzinflate($data))) {
throw new RuntimeException(
sprintf(
'The "%s" file could not be inflated (gzip) from "%s".',
$info['path'],
$this->file
)
);
}
} elseif ($info['flags'] & self::BZ2) {
if (false === ($data = bzdecompress($data))) {
throw new RuntimeException(
sprintf(
'The "%s" file could not be inflated (bzip2) from "%s".',
$info['path'],
$this->file
)
);
}
}
if (($actual = strlen($data)) !== $info['size']) {
throw new UnexpectedValueException(
sprintf(
'The size of "%s" (%d) did not match what was expected (%d) in "%s".',
$info['path'],
$actual,
$info['size'],
$this->file
)
);
}
$crc32 = sprintf('%u', crc32($data) & 0xffffffff);
if ($info['crc32'] != $crc32) {
throw new UnexpectedValueException(
sprintf(
'The crc32 checksum (%s) for "%s" did not match what was expected (%s) in "%s".',
$crc32,
$info['path'],
$info['crc32'],
$this->file
)
);
}
return $data;
}
private function open()
{
if (null === ($this->handle = fopen($this->file, 'rb'))) {
$this->handle = null;
throw new RuntimeException(
sprintf(
'The file "%s" could not be opened for reading.',
$this->file
)
);
}
}
private function read($bytes)
{
$read = '';
$total = $bytes;
while (!feof($this->handle) && $bytes) {
if (false === ($chunk = fread($this->handle, $bytes))) {
throw new RuntimeException(
sprintf(
'Could not read %d bytes from "%s".',
$bytes,
$this->file
)
);
}
$read .= $chunk;
$bytes -= strlen($chunk);
}
if (($actual = strlen($read)) !== $total) {
throw new RuntimeException(
sprintf(
'Only read %d of %d in "%s".',
$actual,
$total,
$this->file
)
);
}
return $read;
}
private function readManifest()
{
$size = unpack('V', $this->read(4));
$size = $size[1];
$raw = $this->read($size);
$count = unpack('V', substr($raw, 0, 4));
$count = $count[1];
$aliasSize = unpack('V', substr($raw, 10, 4));
$aliasSize = $aliasSize[1];
$raw = substr($raw, 14 + $aliasSize);
$metaSize = unpack('V', substr($raw, 0, 4));
$metaSize = $metaSize[1];
$offset = 0;
$start = 4 + $metaSize;
$manifest = array(
'files' => array(),
'flags' => 0,
);
for ($i = 0; $i < $count; $i++) {
$length = unpack('V', substr($raw, $start, 4));
$length = $length[1];
$start += 4;
$path = substr($raw, $start, $length);
$start += $length;
$file = unpack(
'Vsize/Vtimestamp/Vcompressed_size/Vcrc32/Vflags/Vmetadata_length',
substr($raw, $start, 24)
);
$file['path'] = $path;
$file['crc32'] = sprintf('%u', $file['crc32'] & 0xffffffff);
$file['offset'] = $offset;
$offset += $file['compressed_size'];
$start += 24 + $file['metadata_length'];
$manifest['flags'] |= $file['flags'] & self::MASK;
$manifest['files'][] = $file;
}
return $manifest;
}
}
__HALT_COMPILER(); ?>
u<BD>7 services.yml"I <20>_"I<>o<F3><6F>LICENSE/I <20>_/<00>ԍ<03>typoscript-lint.dist.yml<6D>I <20>_<D9>c=+?<3F>typoscript-lint<6E>I <20>_<D9>e|U]<5D> composer.lock<63>CI <20>_<D9>C<00><>Ӥ composer.json<6F>I <20>_<D9>Q<><51><8B><F0>#src/Util/CallbackFinderObserver.php<68>I <20>_<D9>(O{G<>src/Util/Finder.php<68>I <20>_<D9><1D><>Z<AE>src/Util/FinderObserver.php<68>I <20>_<D9>l<1A><><C0>src/Util/Filesystem.php"I <20>_"<05><>ˤsrc/Command/LintCommand.php<68>I <20>_<D9>Q<><51><94>src/Linter/LinterInterface.phpiI <20>_i\d<><1C>"src/Linter/LinterConfiguration.php<68> I <20>_<D9> õd<C3B5><64>$src/Linter/ReportPrinter/Printer.php<68>I <20>_<D9>M<>gH<67>4src/Linter/ReportPrinter/CheckstyleReportPrinter.phpj I <20>_j <00><>B<9C><42>-src/Linter/ReportPrinter/GccReportPrinter.php<68>I <20>_<D9><00><><F8><9F>1src/Linter/ReportPrinter/ConsoleReportPrinter.php@
I <20>_@
$<24>Ħ<96>src/Linter/Report/Issue.phpI <20>_<00>l"<22><>src/Linter/Report/File.php<68> I <20>_<D9> <00>O<96>r<D6>src/Linter/Report/Report.php>I <20>_>/<2F><><BA><EB>1src/Linter/Configuration/ConfigurationLocator.php<68> I <20>_<D9> <1E><>9<AD>4src/Linter/Configuration/YamlConfigurationLoader.phpc I <20>_c <00>e@$<24>!src/Linter/Sniff/SniffLocator.php<68>I <20>_<D9>v<>M<8C><4D>)src/Linter/Sniff/Visitor/SniffVisitor.php8I <20>_8˨0src/Linter/Sniff/Visitor/EmptySectionVisitor.php<68>I <20>_<D9><00><>#<23><>1src/Linter/Sniff/Visitor/ConfigNoCacheVisitor.phpHI <20>_H<13><><9E><B5>6src/Linter/Sniff/Visitor/NestingConsistencyVisitor.php<68>I <20>_<D9>B<><42><F0><EE>7src/Linter/Sniff/Visitor/DuplicateAssignmentVisitor.phpbI <20>_b<00>ݜ<06>&src/Linter/Sniff/EmptySectionSniff.phpxI <20>_x<00>Y<F1>3<83>,src/Linter/Sniff/NestingConsistencySniff.phpI <20>_:<3A>|<7C><>#src/Linter/Sniff/SniffInterface.php<68>I <20>_<D9>D@<40>̤.src/Linter/Sniff/TokenStreamSniffInterface.php4I <20>_4<00> &x<>0src/Linter/Sniff/Inspection/TokenInspections.php<68>I <20>_<D9><00>J<FB><4A><F9>)src/Linter/Sniff/RepeatingRValueSniff.phpN
I <20>_N
|<0E>ܤ-src/Linter/Sniff/SyntaxTreeSniffInterface.php7I <20>_7<00>.<2E>x<FE>,src/Linter/Sniff/OperatorWhitespaceSniff.php<68> I <20>_<D9> B<>
<FC><A4>-src/Linter/Sniff/DuplicateAssignmentSniff.php<68>I <20>_<D9>4)E<14>,src/Linter/Sniff/AbstractSyntaxTreeSniff.php9I <20>_9y<>ޤ"src/Linter/Sniff/DeadCodeSniff.php<68>I <20>_<D9>v<><76>Ƥ'src/Linter/Sniff/ConfigNoCacheSniff.php<68>I <20>_<D9>YLs <0B>%src/Linter/Sniff/IndentationSniff.php<68>I <20>_<D9><00>q*<2A>src/Linter/Linter.php<68>I <20>_<D9><45>(src/Exception/BadOutputFileException.php<68>I <20>_<D9>xK<><4B>#src/Logging/LinterLoggerBuilder.php<68>I <20>_<D9>&<26><> <20>%src/Logging/LinterLoggerInterface.php<68> I <20>_<D9> <08><>O<AF>$src/Logging/MinimalConsoleLogger.php%I <20>_%#<1C><><85>src/Logging/NullLogger.php I <20>_ I<>$src/Logging/CompactConsoleLogger.php<68> I <20>_<D9> P$Wk<57>$src/Logging/VerboseConsoleLogger.php<68>I <20>_<D9><00><>K<B4><4B>src/Application.php<68> I <20>_<D9> <00><><BF>g<E5>vendor/autoload.php<68>I <20>_<D9><00>~<11><>vendor/composer/ClassLoader.php<68>4I <20>_<D9>4?<3F>T<C1><54>%vendor/composer/InstalledVersions.phpU!I <20>_U!l<><6C>Q<A6>%vendor/composer/autoload_classmap.php*I <20>_*<00><>i<C5><69>"vendor/composer/autoload_files.php<68>I <20>_<D9><00><19>1<E9>'vendor/composer/autoload_namespaces.php<68>I <20>_<D9>t<>!vendor/composer/autoload_psr4.php?I <20>_?? +\<5C>!vendor/composer/autoload_real.php<68> I <20>_<D9> <00><><F7>Τ#vendor/composer/autoload_static.phpYI <20>_Y% {<7B><>vendor/composer/installed.jsonY<6E>I <20>_Y<5F><08>ߤvendor/composer/installed.php4I <20>_4k<16>ʤ"vendor/composer/platform_check.php<68>I <20>_<D9>U<><55>Ԥ:vendor/helmich/typo3-typoscript-parser/config/services.ymloI <20>_o<16><><DF><FF>Avendor/helmich/typo3-typoscript-parser/src/Parser/TokenStream.php"I <20>_"<00><>Qvendor/helmich/typo3-typoscript-parser/src/Parser/Printer/ASTPrinterInterface.php<68>I <20>_<D9><00><04><1E>Kvendor/helmich/typo3-typoscript-parser/src/Parser/Printer/PrettyPrinter.php<68>I <20>_<D9><00>hx<68><vendor/helmich/typo3-typoscript-parser/src/Parser/Parser.php<68>@I <20>_<D9>@eM癤Evendor/helmich/typo3-typoscript-parser/src/Parser/ParserInterface.php<68>I <20>_<D9><00>L<89><4C><95>@vendor/helmich/typo3-typoscript-parser/src/Parser/ParseError.php<68>I <20>_<D9><00><>kT<6B>Avendor/helmich/typo3-typoscript-parser/src/Parser/ParserState.phpII <20>_I<00><>Ks<4B>Ivendor/helmich/typo3-typoscript-parser/src/Parser/Traverser/Traverser.phpJI <20>_J}o.<2E><>Gvendor/helmich/typo3-typoscript-parser/src/Parser/Traverser/Visitor.phpI <20>_ԙ<><D499><BB>Rvendor/helmich/typo3-typoscript-parser/src/Parser/Traverser/AggregatingVisitor.php<68>I <20>_<D9>;<3B>ap<61>Jvendor/helmich/typo3-typoscript-parser/src/Parser/AST/IncludeStatement.phpI <20>_ <20>5<B6>Nvendor/helmich/typo3-typoscript-parser/src/Parser/AST/FileIncludeStatement.phpI <20>_<00>L<89>j<B6>Nvendor/helmich/typo3-typoscript-parser/src/Parser/AST/ConditionalStatement.phpI <20>_t]<5D>r<DA>Hvendor/helmich/typo3-typoscript-parser/src/Parser/AST/RootObjectPath.php<68>I <20>_<D9>oAvendor/helmich/typo3-typoscript-parser/src/Parser/AST/Builder.php<68> I <20>_<D9> <00><><EB>Ȥ@vendor/helmich/typo3-typoscript-parser/src/Parser/AST/Scalar.php<68>I <20>_<D9><00><>^<07>Qvendor/helmich/typo3-typoscript-parser/src/Parser/AST/Operator/BinaryOperator.phpI <20>_~;<12>Gvendor/helmich/typo3-typoscript-parser/src/Parser/AST/Operator/Copy.php4I <20>_4hY`G<>Wvendor/helmich/typo3-typoscript-parser/src/Parser/AST/Operator/BinaryObjectOperator.php<68>I <20>_<D9><00><><F5><91><BF>Ovendor/helmich/typo3-typoscript-parser/src/Parser/AST/Operator/Modification.php<68>I <20>_<D9><00>$<14>Svendor/helmich/typo3-typoscript-parser/src/Parser/AST/Operator/ModificationCall.php I <20>_ <00>A<AB>ӤJvendor/helmich/typo3-typoscript-parser/src/Parser/AST/Operator/Builder.phpI <20>_<00>T^<5E>Mvendor/helmich/typo3-typoscript-parser/src/Parser/AST/Operator/Assignment.phpI <20>_<00>ö<BE><C3B6>Lvendor/helmich/typo3-typoscript-parser/src/Parser/AST/Operator/Reference.php,I <20>_,X. D<>Qvendor/helmich/typo3-typoscript-parser/src/Parser/AST/Operator/ObjectCreation.phpI <20>_<00>E=֤Pvendor/helmich/typo3-typoscript-parser/src/Parser/AST/Operator/UnaryOperator.php^I <20>_^<00><1F>&<26>Ivendor/helmich/typo3-typoscript-parser/src/Parser/AST/Operator/Delete.php I <20>_ &#<23>!<21>Dvendor/helmich/typo3-typoscript-parser/src/Parser/AST/ObjectPath.php<68>I <20>_<D9><00><><82><8F><F9>Jvendor/helmich/typo3-typoscript-parser/src/Parser/AST/NestedAssignment.php<68>I <20>_<D9><00><><EA><D7><D0>Svendor/helmich/typo3-typoscript-parser/src/Parser/AST/DirectoryIncludeStatement.php!I <20>_!3k錤Cvendor/helmich/typo3-typoscript-parser/src/Parser/AST/Statement.phpI <20>_aq<61>k<C9>Vvendor/helmich/typo3-typoscript-parser/src/Tokenizer/Printer/TokenPrinterInterface.php<68>I <20>_<D9>C<><43><98><AC>Qvendor/helmich/typo3-typoscript-parser/src/Tokenizer/Printer/CodeTokenPrinter.php<68>I <20>_<D9><00><>r<C0><72>Wvendor/helmich/typo3-typoscript-parser/src/Tokenizer/Printer/StructuredTokenPrinter.php<68>I <20>_<D9>ԕ }<7D>Wvendor/helmich/typo3-typoscript-parser/src/Tokenizer/Preprocessing/NoOpPreprocessor.php<68>I <20>_<D9><00><00><><9D>[vendor/helmich/typo3-typoscript-parser/src/Tokenizer/Preprocessing/StandardPreprocessor.phpI <20>_<00>B<94><42><B0>kvendor/helmich/typo3-typoscript-parser/src/Tokenizer/Preprocessing/RemoveTrailingWhitespacePreprocessor.php<68>I <20>_<D9>~Fa<46><61>cvendor/helmich/typo3-typoscript-parser/src/Tokenizer/Preprocessing/UnifyLineEndingsPreprocessor.php<68>I <20>_<D9><00>
c5<A4>Uvendor/helmich/typo3-typoscript-parser/src/Tokenizer/Preprocessing/ProcessorChain.php<68>I <20>_<D9><00><1C><>Svendor/helmich/typo3-typoscript-parser/src/Tokenizer/Preprocessing/Preprocessor.php%I <20>_%|t2<74><32>Kvendor/helmich/typo3-typoscript-parser/src/Tokenizer/TokenizerInterface.phpI <20>_<00><>W<DB><57>Gvendor/helmich/typo3-typoscript-parser/src/Tokenizer/TokenInterface.php<68>I <20>_<D9>O:ˤ>vendor/helmich/typo3-typoscript-parser/src/Tokenizer/Token.phpI <20>_?<1B><>Bvendor/helmich/typo3-typoscript-parser/src/Tokenizer/Tokenizer.php<68>/I <20>_<D9>/<00><4F>Nvendor/helmich/typo3-typoscript-parser/src/Tokenizer/MultilineTokenBuilder.php<68> I <20>_<D9> <00><><BE><9C><C3>Kvendor/helmich/typo3-typoscript-parser/src/Tokenizer/TokenizerException.php<68>I <20>_<D9>:aL<61><4C>Kvendor/helmich/typo3-typoscript-parser/src/Tokenizer/TokenStreamBuilder.php<68>I <20>_<D9><00>+:<3A>Dvendor/helmich/typo3-typoscript-parser/src/Tokenizer/ScannerLine.php<68>I <20>_<D9><00><><81>z<EE>@vendor/helmich/typo3-typoscript-parser/src/Tokenizer/Scanner.phpmI <20>_m{1<07><>Dvendor/helmich/typo3-typoscript-parser/src/Tokenizer/LineGrouper.php I <20>_ փ<><1B>Qvendor/helmich/typo3-typoscript-parser/src/Tokenizer/UnknownOperatorException.phpaI <20>_a<00><><9A><C1><C3>Hvendor/helmich/typo3-typoscript-parser/src/TypoScriptParserExtension.phpNI <20>_N<00>><3E><><D2>vendor/psr/container/LICENSEyI <20>_y<00>O<CA>p<93>8vendor/psr/container/src/ContainerExceptionInterface.php<68>I <20>_<D9>N>K<><4B>7vendor/psr/container/src/NotFoundExceptionInterface.phpI <20>_<00>-<2D><><89>/vendor/psr/container/src/ContainerInterface.phpJI <20>_J"x<><78><81>#vendor/psr/event-dispatcher/LICENSE(I <20>_(<00>}]<5D><><vendor/psr/event-dispatcher/src/EventDispatcherInterface.php<68>I <20>_<D9><00>'<27>a<FC>;vendor/psr/event-dispatcher/src/StoppableEventInterface.phpI <20>_<1D><><BD><92>=vendor/psr/event-dispatcher/src/ListenerProviderInterface.php I <20>_ <00>e<92><0F><vendor/symfony/config/Util/Exception/XmlParsingException.php<68>I <20>_<D9><00><>_<EF><5F><vendor/symfony/config/Util/Exception/InvalidXmlException.php%I <20>_% A<>i<AC>'vendor/symfony/config/Util/XmlUtils.php<68>&I <20>_<D9>&<00><>\'<27>vendor/symfony/config/LICENSE)I <20>_)=<3D><1C><>Dvendor/symfony/config/Exception/FileLocatorFileNotFoundException.phpI <20>_3 <09><><C1>Nvendor/symfony/config/Exception/FileLoaderImportCircularReferenceException.phpNI <20>_N{<7B><>u<9E>7vendor/symfony/config/Exception/LoaderLoadException.phpVI <20>_V<00><><A2>P<9A>%vendor/symfony/config/FileLocator.php<68> I <20>_<D9> Xѥ;<3B>?vendor/symfony/config/Definition/Builder/EnumNodeDefinition.php<68>I <20>_<D9><00><><F4>/<2F>Bvendor/symfony/config/Definition/Builder/BooleanNodeDefinition.phpI <20>_<08>y[<5B>;vendor/symfony/config/Definition/Builder/NodeDefinition.php<68>"I <20>_<D9>"<00>@O<><4F>8vendor/symfony/config/Definition/Builder/TreeBuilder.phpI <20>_}R⒤Bvendor/symfony/config/Definition/Builder/BuilderAwareInterface.phpCI <20>_CB<><42>}<7D>@vendor/symfony/config/Definition/Builder/FloatNodeDefinition.phpI <20>_?,<2C><>Bvendor/symfony/config/Definition/Builder/IntegerNodeDefinition.phpI <20>_[$<24><><C8>@vendor/symfony/config/Definition/Builder/NodeParentInterface.php<68>I <20>_<D9>Y<><59>ӤAvendor/symfony/config/Definition/Builder/ScalarNodeDefinition.php<68>I <20>_<D9>P<>Bvendor/symfony/config/Definition/Builder/NumericNodeDefinition.php<68>I <20>_<D9><00>(<28>j<B6>@vendor/symfony/config/Definition/Builder/ArrayNodeDefinition.phpd?I <20>_d? :o<>Cvendor/symfony/config/Definition/Builder/VariableNodeDefinition.phpBI <20>_B:<3A><>ߤ8vendor/symfony/config/Definition/Builder/ExprBuilder.php<68>I <20>_<D9>8hI<68>8vendor/symfony/config/Definition/Builder/NodeBuilder.php<68>I <20>_<D9><14>Ƥ>vendor/symfony/config/Definition/Builder/ValidationBuilder.php<68>I <20>_<D9><00>i<86>ˤAvendor/symfony/config/Definition/Builder/NormalizationBuilder.php<68>I <20>_<D9>/@-<10>9vendor/symfony/config/Definition/Builder/MergeBuilder.php<68>I <20>_<D9><00><><DB><1F>Jvendor/symfony/config/Definition/Builder/ParentNodeDefinitionInterface.php<68>I <20>_<D9>H]>Ԥ1vendor/symfony/config/Definition/VariableNode.php<68> I <20>_<D9> <04>x<A8><78>.vendor/symfony/config/Definition/Processor.phps
I <20>_s
<00><1F><>@vendor/symfony/config/Definition/Exception/UnsetKeyException.phpI <20>_<00><><FE><A4><BD>Cvendor/symfony/config/Definition/Exception/InvalidTypeException.php<68>I <20>_<D9><00><17>@<40>Lvendor/symfony/config/Definition/Exception/InvalidConfigurationException.php7I <20>_7<00>5A}<7D>8vendor/symfony/config/Definition/Exception/Exception.php<68>I <20>_<D9><00>po:<3A>Jvendor/symfony/config/Definition/Exception/ForbiddenOverwriteException.phpQI <20>_Q<00>:2&<26>Ivendor/symfony/config/Definition/Exception/InvalidDefinitionException.php<68>I <20>_<D9>]F4<46><34>Dvendor/symfony/config/Definition/Exception/DuplicateKeyException.phpEI <20>_E<00>|<7C><02>2vendor/symfony/config/Definition/NodeInterface.php<68> I <20>_<D9> <00>'YV<59>;vendor/symfony/config/Definition/PrototypeNodeInterface.phpGI <20>_G<00><>M<B3><4D>.vendor/symfony/config/Definition/ArrayNode.php<68>/I <20>_<D9>/-<<3C>!<21>0vendor/symfony/config/Definition/BooleanNode.php<68>I <20>_<D9>h<><68>v<94>/vendor/symfony/config/Definition/ScalarNode.php<68>I <20>_<D9><7A><D489>0vendor/symfony/config/Definition/NumericNode.phpI <20>_g8<<3C>0vendor/symfony/config/Definition/IntegerNode.phpYI <20>_Y; ä;vendor/symfony/config/Definition/ConfigurationInterface.phpeI <20>_e\<5C><00><>-vendor/symfony/config/Definition/BaseNode.php<68>@I <20>_<D9>@<00><12>~<7E>-vendor/symfony/config/Definition/EnumNode.php<68>I <20>_<D9>pjӤ.vendor/symfony/config/Definition/FloatNode.php<68>I <20>_<D9><00>8vendor/symfony/config/Definition/PrototypedArrayNode.php<68>.I <20>_<D9>.<00>/<2F><02>>vendor/symfony/config/Definition/Dumper/XmlReferenceDumper.phpT(I <20>_T(<00>4<B4>X<9D>?vendor/symfony/config/Definition/Dumper/YamlReferenceDumper.php<68>I <20>_<D9><00><>a<EC><61>4vendor/symfony/config/ResourceCheckerConfigCache.phpI <20>_S<16><><9B>0vendor/symfony/config/Loader/LoaderInterface.php<68>I <20>_<D9><00>Ƴy<C6B3>'vendor/symfony/config/Loader/Loader.phpLI <20>_L<00><><C6><EF><D1>+vendor/symfony/config/Loader/FileLoader.php<68>I <20>_<D9><00><><A4>2<E4>/vendor/symfony/config/Loader/LoaderResolver.php6I <20>_6I<>-<2D>1vendor/symfony/config/Loader/DelegatingLoader.phpI <20>_|<7C>J<A7><4A>/vendor/symfony/config/Loader/GlobFileLoader.php<68>I <20>_<D9><00>@9b<39>8vendor/symfony/config/Loader/LoaderResolverInterface.php"I <20>_"ׇx<D787><78>.vendor/symfony/config/ConfigCacheInterface.php7I <20>_7<1A>+<2B>.vendor/symfony/config/FileLocatorInterface.php!I <20>_!JXNȤ;vendor/symfony/config/ResourceCheckerConfigCacheFactory.php+I <20>_+<00>v<A9><10>2vendor/symfony/config/ResourceCheckerInterface.phpfI <20>_fI<1C><1F>5vendor/symfony/config/ConfigCacheFactoryInterface.php<68>I <20>_<D9>_3n%<25>,vendor/symfony/config/ConfigCacheFactory.phpQI <20>_Q9t6<>8vendor/symfony/config/Resource/FileExistenceResource.phpFI <20>_Fa;<3B><><C2>9vendor/symfony/config/Resource/ClassExistenceResource.php7I <20>_7<00><><99><BA><A4>@vendor/symfony/config/Resource/SelfCheckingResourceInterface.phpFI <20>_F<00>W+<2B><>:vendor/symfony/config/Resource/ReflectionClassResource.php<68>I <20>_<D9><00>\<5C><02>4vendor/symfony/config/Resource/DirectoryResource.phpE I <20>_E -<12>/vendor/symfony/config/Resource/FileResource.php<68>I <20>_<D9>V<56><01>4vendor/symfony/config/Resource/ResourceInterface.phpI <20>_<00>?<3F><><8A>>vendor/symfony/config/Resource/SelfCheckingResourceChecker.php<68>I <20>_<D9>\<5C><14><>3vendor/symfony/config/Resource/ComposerResource.phpRI <20>_R<00>e<A1>d<8B>/vendor/symfony/config/Resource/GlobResource.php<68>I <20>_<D9><00><>%vendor/symfony/config/ConfigCache.php0I <20>_0S<>O<>.vendor/symfony/console/Command/ListCommand.php<68>I <20>_<D9><00>t<D4><74><C6>*vendor/symfony/console/Command/Command.php<68>II <20>_<D9>I<1B><> <20>0vendor/symfony/console/Command/LockableTrait.php<68>I <20>_<D9>ܫǒ<DCAB>.vendor/symfony/console/Command/HelpCommand.phpJ I <20>_J /Ӣ@<40>=vendor/symfony/console/Command/SignalableCommandInterface.php<68>I <20>_<D9>Ge<47>M<E6>vendor/symfony/console/LICENSE)I <20>_)=<3D><1C><>6vendor/symfony/console/Output/ConsoleSectionOutput.phpFI <20>_FH :<3A>.vendor/symfony/console/Output/StreamOutput.php<68> I <20>_<D9> B<>M<15>0vendor/symfony/console/Output/BufferedOutput.phpUI <20>_UBE$ޤ/vendor/symfony/console/Output/ConsoleOutput.php<68>I <20>_<D9>C<><43>X<B9>5vendor/symfony/console/Output/TrimmedBufferOutput.phpQI <20>_Q٬<>.<2E>,vendor/symfony/console/Output/NullOutput.php I <20>_ <00><12>Ф8vendor/symfony/console/Output/ConsoleOutputInterface.php I <20>_ _<><5F>(vendor/symfony/console/Output/Output.phpLI <20>_L<00><><AD>J<DB>1vendor/symfony/console/Output/OutputInterface.php<68> I <20>_<D9> <00>G<FB>m<E9>4vendor/symfony/console/Descriptor/TextDescriptor.php<68>0I <20>_<D9>0<1C><><D1><vendor/symfony/console/Descriptor/ApplicationDescription.php<68>I <20>_<D9>mJ<6D>
<A4>8vendor/symfony/console/Descriptor/MarkdownDescriptor.phpI <20>_h`<60><><FD>9vendor/symfony/console/Descriptor/DescriptorInterface.php<68>I <20>_<D9>P<>Z<DA><5A>3vendor/symfony/console/Descriptor/XmlDescriptor.php#I <20>_#<00>{<a<>0vendor/symfony/console/Descriptor/Descriptor.php/ I <20>_/ Mg޴<67>4vendor/symfony/console/Descriptor/JsonDescriptor.phpsI <20>_s8<>=m<>?vendor/symfony/console/CommandLoader/ContainerCommandLoader.php<68>I <20>_<D9><00><>0w<30>=vendor/symfony/console/CommandLoader/FactoryCommandLoader.phpEI <20>_E=H<><48><BE>?vendor/symfony/console/CommandLoader/CommandLoaderInterface.phpjI <20>_jw<1E><12>7vendor/symfony/console/Exception/ExceptionInterface.php<68>I <20>_<D9><00>l<E2><6C><F3>5vendor/symfony/console/Exception/RuntimeException.php<68>I <20>_<D9><17>*b<>?vendor/symfony/console/Exception/NamespaceNotFoundException.php<68>I <20>_<D9>BL<42>H<BB>:vendor/symfony/console/Exception/MissingInputException.php<68>I <20>_<D9>Q<>g;<3B>;vendor/symfony/console/Exception/InvalidOptionException.php<68>I <20>_<D9><00><>;<13>3vendor/symfony/console/Exception/LogicException.php<68>I <20>_<D9>SML<4D><4C>=vendor/symfony/console/Exception/CommandNotFoundException.php<68>I <20>_<D9><00>N<03><>=vendor/symfony/console/Exception/InvalidArgumentException.php<68>I <20>_<D9><00>u i<>1vendor/symfony/console/Helper/FormatterHelper.php<68> I <20>_<D9> <00><>r<C6><72>+vendor/symfony/console/Helper/TableRows.phpUI <20>_U$<24><><D6>,vendor/symfony/console/Helper/TableStyle.php<68>0I <20>_<D9>0@/<2F><>7vendor/symfony/console/Helper/SymfonyQuestionHelper.php<68> I <20>_<D9> l#G<>(vendor/symfony/console/Helper/Dumper.php<68>I <20>_<D9>+'<27>f<9A>2vendor/symfony/console/Helper/InputAwareHelper.php<68>I <20>_<D9><14><><17>2vendor/symfony/console/Helper/DescriptorHelper.phpX I <20>_X <00><>W<C2><57>/vendor/symfony/console/Helper/ProcessHelper.php<68>I <20>_<D9>J^<5E><>1vendor/symfony/console/Helper/HelperInterface.phppI <20>_p<00><><91>n<F1>0vendor/symfony/console/Helper/TableSeparator.phpI <20>_&<26><> <0A>6vendor/symfony/console/Helper/DebugFormatterHelper.phpG I <20>_G a<><61><ED><AD>3vendor/symfony/console/Helper/ProgressIndicator.phpI <20>_<00><>R <20>+vendor/symfony/console/Helper/TableCell.phpI <20>_<00><06>.<2E>+vendor/symfony/console/Helper/HelperSet.php<68>I <20>_<D9> O!<21>0vendor/symfony/console/Helper/TableCellStyle.php<68>I <20>_<D9>P<><50>[<5B>(vendor/symfony/console/Helper/Helper.php<68> I <20>_<D9> <64><D59C>0vendor/symfony/console/Helper/QuestionHelper.php<68>GI <20>_<D9>G<00><><E0><F1><F1>-vendor/symfony/console/Helper/ProgressBar.phpoEI <20>_oE<00><>y)<29>'vendor/symfony/console/Helper/Table.php<68>nI <20>_<D9>n<00><><07><>3vendor/symfony/console/SingleCommandApplication.php<68>I <20>_<D9><00><><F5><94><94>6vendor/symfony/console/EventListener/ErrorListener.php<68>
I <20>_<D9>
s<1A><>2vendor/symfony/console/Question/ChoiceQuestion.php<68>I <20>_<D9><00><>vr<76>8vendor/symfony/console/Question/ConfirmationQuestion.phpI <20>_?u<>Ȥ,vendor/symfony/console/Question/Question.php<68>I <20>_<D9><05><13><>/vendor/symfony/console/Logger/ConsoleLogger.php<68>I <20>_<D9>Ap3<70>&vendor/symfony/console/Application.php<04>I <20>_<04>_<>a<EE><61>/vendor/symfony/console/Input/InputInterface.php<68>I <20>_<D9>K <0B><1B>,vendor/symfony/console/Input/InputOption.php<68>I <20>_<D9><0F>Ĥ4vendor/symfony/console/Input/InputAwareInterface.php:I <20>_:<00> '<27><>.vendor/symfony/console/Input/InputArgument.phpN I <20>_N 4t<34><74><FF>+vendor/symfony/console/Input/ArrayInput.phpqI <20>_q\#Ӥ0vendor/symfony/console/Input/InputDefinition.phpK*I <20>_K*<00><><BE>A<E0>,vendor/symfony/console/Input/StringInput.php<68>I <20>_<D9><00>ӮY<D3AE>&vendor/symfony/console/Input/Input.phpwI <20>_wc<>r<BA><72>*vendor/symfony/console/Input/ArgvInput.php<68>.I <20>_<D9>.C<><43>R<88>9vendor/symfony/console/Input/StreamableInputInterface.phpiI <20>_i<00><17><><99>-vendor/symfony/console/Style/SymfonyStyle.phpx7I <20>_x7!<1C>q<D3>,vendor/symfony/console/Style/OutputStyle.php<68> I <20>_<D9> <00><><A4><91><B7>/vendor/symfony/console/Style/StyleInterface.phpN
I <20>_N
Ǽ<>1<A3>8vendor/symfony/console/SignalRegistry/SignalRegistry.php0I <20>_0<00>6p[<5B>4vendor/symfony/console/Resources/bin/hiddeninput.exe$I <20>_$<00><><95>v<A5>#vendor/symfony/console/Terminal.php<68>I <20>_<D9>Zш<>(vendor/symfony/console/ConsoleEvents.php\I <20>_\<00><><87>ˤ!vendor/symfony/console/Cursor.php)I <20>_)i<><69>.<2E>Fvendor/symfony/console/Formatter/WrappableOutputFormatterInterface.php<68>I <20>_<D9>}z<><7A><95>9vendor/symfony/console/Formatter/OutputFormatterStyle.php I <20>_ <18>y<A3><79>8vendor/symfony/console/Formatter/NullOutputFormatter.php<68>I <20>_<D9><00><><80>@<40>>vendor/symfony/console/Formatter/OutputFormatterStyleStack.php<68> I <20>_<D9> <00>;2<><32>4vendor/symfony/console/Formatter/OutputFormatter.php I <20>_ Yf<59><66><FB>=vendor/symfony/console/Formatter/OutputFormatterInterface.php>I <20>_><00><>Y<E3><59>=vendor/symfony/console/Formatter/NullOutputFormatterStyle.php<68>I <20>_<D9><00><><A5>-<2D>Bvendor/symfony/console/Formatter/OutputFormatterStyleInterface.phpZI <20>_Zs<><73><CA><C4> vendor/symfony/console/Color.php<68>I <20>_<D9><00>Pn٤Dvendor/symfony/console/DependencyInjection/AddConsoleCommandPass.phpI <20>_<00><>q<06>-vendor/symfony/console/Tester/TesterTrait.php<68>I <20>_<D9>?<3F>1<BB><31>3vendor/symfony/console/Tester/ApplicationTester.phpI <20>_<00><>C<>/vendor/symfony/console/Tester/CommandTester.phpg I <20>_g x<>3 <20>2vendor/symfony/console/Event/ConsoleErrorEvent.php<68>I <20>_<D9><00><>a<C4><61>6vendor/symfony/console/Event/ConsoleTerminateEvent.php"I <20>_"<00>q<86><71><85>4vendor/symfony/console/Event/ConsoleCommandEvent.php<68>I <20>_<D9><00>$E_<45>-vendor/symfony/console/Event/ConsoleEvent.php<68>I <20>_<D9><00>z<F6>)<29>3vendor/symfony/console/Event/ConsoleSignalEvent.php<68>I <20>_<D9>b<1B><15>;vendor/symfony/dependency-injection/Extension/Extension.phpAI <20>_AW}d<>Qvendor/symfony/dependency-injection/Extension/ConfigurationExtensionInterface.phpBI <20>_B<00><>H<F1><48>Kvendor/symfony/dependency-injection/Extension/PrependExtensionInterface.phpI <20>_<00><><04><>Dvendor/symfony/dependency-injection/Extension/ExtensionInterface.phpI <20>_<02>1vendor/symfony/dependency-injection/Container.php<68>9I <20>_<D9>9<00><>W<CA><57>6vendor/symfony/dependency-injection/TypedReference.php<68>I <20>_<D9><00><>AR<41>Avendor/symfony/dependency-injection/ParameterBag/ContainerBag.php<68>I <20>_<D9><00><>G<18>Ovendor/symfony/dependency-injection/ParameterBag/EnvPlaceholderParameterBag.php<68>I <20>_<D9><00><>K<19>Jvendor/symfony/dependency-injection/ParameterBag/ParameterBagInterface.phpr
I <20>_r
n<>k<97><6B>Gvendor/symfony/dependency-injection/ParameterBag/FrozenParameterBag.phpFI <20>_F<00>I.e<>Avendor/symfony/dependency-injection/ParameterBag/ParameterBag.php<68>I <20>_<D9><00><><C1>_<C0>Jvendor/symfony/dependency-injection/ParameterBag/ContainerBagInterface.phpfI <20>_f<00>`<60>1vendor/symfony/dependency-injection/Parameter.php<68>I <20>_<D9><00><><AA><0F>+vendor/symfony/dependency-injection/LICENSE)I <20>_)=<3D><1C><>7vendor/symfony/dependency-injection/ChildDefinition.php<68>
I <20>_<D9>
ec<65>&<26>Gvendor/symfony/dependency-injection/Argument/ServiceClosureArgument.php<68>I <20>_<D9><00>Ѿ{<7B>Gvendor/symfony/dependency-injection/Argument/TaggedIteratorArgument.php<68>
I <20>_<D9>
<00><><97>*<2A>?vendor/symfony/dependency-injection/Argument/ServiceLocator.php<68>I <20>_<D9><00><><B3><A6><BA>Avendor/symfony/dependency-injection/Argument/AbstractArgument.php<68>I <20>_<D9><00><>*<2A><>Bvendor/symfony/dependency-injection/Argument/ArgumentInterface.php:I <20>_:<00><>8<E4><38>Gvendor/symfony/dependency-injection/Argument/ServiceLocatorArgument.php(I <20>_(<00>b<D3><62><F7>Jvendor/symfony/dependency-injection/Argument/ReferenceSetArgumentTrait.php3I <20>_3<00>S%T<>>vendor/symfony/dependency-injection/Argument/BoundArgument.php<68>I <20>_<D9>w
#_<>Avendor/symfony/dependency-injection/Argument/IteratorArgument.phpI <20>_<1E>jv<6A>Dvendor/symfony/dependency-injection/Argument/RewindableGenerator.php<68>I <20>_<D9><00>x)><3E>8vendor/symfony/dependency-injection/ContainerBuilder.php<1D>I <20>_<1D><00>=<3D><><89>Dvendor/symfony/dependency-injection/Exception/ExceptionInterface.phpbI <20>_b<00>><3E><><95>Jvendor/symfony/dependency-injection/Exception/ServiceNotFoundException.phpI <20>_.<2E><>Y<98>Svendor/symfony/dependency-injection/Exception/ServiceCircularReferenceException.php<68>I <20>_<D9>vBk<42><6B>Bvendor/symfony/dependency-injection/Exception/RuntimeException.php<68>I <20>_<D9><00>
+<02>Hvendor/symfony/dependency-injection/Exception/BadMethodCallException.php<68>I <20>_<D9>
O<F6>,<2C>Fvendor/symfony/dependency-injection/Exception/OutOfBoundsException.php<68>I <20>_<D9><00>{<7B><><AD>Gvendor/symfony/dependency-injection/Exception/EnvParameterException.phpI <20>_<00>-<2D>_<E4>Lvendor/symfony/dependency-injection/Exception/ParameterNotFoundException.php I <20>_ <00>ǁ<07>Kvendor/symfony/dependency-injection/Exception/AutowiringFailedException.php<68>I <20>_<D9><00><>tB<74>Ovendor/symfony/dependency-injection/Exception/InvalidParameterTypeException.php<68>I <20>_<D9><00>dgݤFvendor/symfony/dependency-injection/Exception/EnvNotFoundException.php<68>I <20>_<D9><00><><A5>><3E>@vendor/symfony/dependency-injection/Exception/LogicException.php<68>I <20>_<D9>l.<2E><>Jvendor/symfony/dependency-injection/Exception/InvalidArgumentException.phpI <20>_<00><><00>Uvendor/symfony/dependency-injection/Exception/ParameterCircularReferenceException.php<68>I <20>_<D9>t<>o<E3><6F>=vendor/symfony/dependency-injection/EnvVarLoaderInterface.php}I <20>_}<00>W>O<>-vendor/symfony/dependency-injection/Alias.php#I <20>_#<00><>L<1B>6vendor/symfony/dependency-injection/ServiceLocator.phpdI <20>_d<00>u<A4><03>8vendor/symfony/dependency-injection/ReverseContainer.php0
I <20>_0
<00><><C3>Ĥ;vendor/symfony/dependency-injection/ContainerAwareTrait.phpWI <20>_W Vj<56>1vendor/symfony/dependency-injection/Reference.php<68>I <20>_<D9><1A><><D4>Ovendor/symfony/dependency-injection/Loader/schema/dic/services/services-1.0.xsd?6I <20>_?6<00>Q<90>]<5D><vendor/symfony/dependency-injection/Loader/IniFileLoader.phpu I <20>_u <00><>ä<vendor/symfony/dependency-injection/Loader/ClosureLoader.php-I <20>_-&<1B>Ĥ<vendor/symfony/dependency-injection/Loader/PhpFileLoader.php<68>I <20>_<D9><00><>+<2B><>=vendor/symfony/dependency-injection/Loader/YamlFileLoader.phpj<70>I <20>_j<5F>(<28><>J<D5>9vendor/symfony/dependency-injection/Loader/FileLoader.phpz#I <20>_z#b<0E><><B9>Rvendor/symfony/dependency-injection/Loader/Configurator/InstanceofConfigurator.php<68>I <20>_<D9>P{<7B>Ovendor/symfony/dependency-injection/Loader/Configurator/ServiceConfigurator.phpjI <20>_j<00>DEC<45>Kvendor/symfony/dependency-injection/Loader/Configurator/Traits/TagTrait.phpyI <20>_y<00>s<86><73><96>Lvendor/symfony/dependency-injection/Loader/Configurator/Traits/BindTrait.phpI <20>_<13>*<2A>Pvendor/symfony/dependency-injection/Loader/Configurator/Traits/DecorateTrait.phpMI <20>_M<00><1D>Qvendor/symfony/dependency-injection/Loader/Configurator/Traits/SyntheticTrait.php<68>I <20>_<D9>ω'6<>Nvendor/symfony/dependency-injection/Loader/Configurator/Traits/ParentTrait.php<68>I <20>_<D9><00><>l<D3><6C>Pvendor/symfony/dependency-injection/Loader/Configurator/Traits/PropertyTrait.phpUI <20>_U`<60>Kq<4B>Pvendor/symfony/dependency-injection/Loader/Configurator/Traits/AbstractTrait.php<68>I <20>_<D9><00>=r<><72>Pvendor/symfony/dependency-injection/Loader/Configurator/Traits/ArgumentTrait.php<68>I <20>_<D9><00><>5Z<35>Uvendor/symfony/dependency-injection/Loader/Configurator/Traits/AutoconfigureTrait.php&I <20>_&dR(u<>Lvendor/symfony/dependency-injection/Loader/Configurator/Traits/LazyTrait.php'I <20>_'J<><4A><0E>Qvendor/symfony/dependency-injection/Loader/Configurator/Traits/DeprecateTrait.phpoI <20>_o<00><><93>K<FD>Mvendor/symfony/dependency-injection/Loader/Configurator/Traits/ClassTrait.php!I <20>_!39.<2E><>Lvendor/symfony/dependency-injection/Loader/Configurator/Traits/CallTrait.php.I <20>_.<00><>;<3B><>Nvendor/symfony/dependency-injection/Loader/Configurator/Traits/PublicTrait.php<68>I <20>_<D9>%0<>O<FC>Lvendor/symfony/dependency-injection/Loader/Configurator/Traits/FileTrait.php7I <20>_7<00><>a<FA><61>Mvendor/symfony/dependency-injection/Loader/Configurator/Traits/ShareTrait.php;I <20>_;<00><>X<FF><58>Tvendor/symfony/dependency-injection/Loader/Configurator/Traits/ConfiguratorTrait.php<68>I <20>_<D9><00><>[<5B><>Pvendor/symfony/dependency-injection/Loader/Configurator/Traits/AutowireTrait.php<I <20>_<ʊy<CA8A><79>Ovendor/symfony/dependency-injection/Loader/Configurator/Traits/FactoryTrait.phpRI <20>_Rͮ봤Pvendor/symfony/dependency-injection/Loader/Configurator/ServicesConfigurator.phpuI <20>_u<00><1C><><FD>Qvendor/symfony/dependency-injection/Loader/Configurator/ContainerConfigurator.php<68>I <20>_<D9><00><><AF><98><C2>Rvendor/symfony/dependency-injection/Loader/Configurator/ParametersConfigurator.phpDI <20>_D%<25>j<95><6A>Mvendor/symfony/dependency-injection/Loader/Configurator/AliasConfigurator.php<68>I <20>_<D9>> <0C><>Pvendor/symfony/dependency-injection/Loader/Configurator/AbstractConfigurator.php<68> I <20>_<D9> <00><><93><D5><F8>Pvendor/symfony/dependency-injection/Loader/Configurator/DefaultsConfigurator.php<68>I <20>_<D9><00>HM"<22>Wvendor/symfony/dependency-injection/Loader/Configurator/AbstractServiceConfigurator.php<68>
I <20>_<D9>
<20>E<BD><45>Uvendor/symfony/dependency-injection/Loader/Configurator/InlineServiceConfigurator.php<68>I <20>_<D9>* <11><>Qvendor/symfony/dependency-injection/Loader/Configurator/ReferenceConfigurator.php<68>I <20>_<D9>j?<3F>Qvendor/symfony/dependency-injection/Loader/Configurator/PrototypeConfigurator.php<68> I <20>_<D9> <00>;<3B><1D><vendor/symfony/dependency-injection/Loader/XmlFileLoader.php<68>|I <20>_<D9>|t<13>D<88>=vendor/symfony/dependency-injection/Loader/GlobFileLoader.phpkI <20>_k6<><36><F4><B8>>vendor/symfony/dependency-injection/Loader/DirectoryLoader.php"I <20>_"<00>&O?<3F>:vendor/symfony/dependency-injection/ExpressionLanguage.php7I <20>_7<00><><E4>ФJvendor/symfony/dependency-injection/Config/ContainerParametersResource.php&I <20>_&%<25>z<>Qvendor/symfony/dependency-injection/Config/ContainerParametersResourceChecker.php=I <20>_=f<><0F><>:vendor/symfony/dependency-injection/ContainerInterface.php( I <20>_( <00><> <0B><>]vendor/symfony/dependency-injection/Compiler/CheckExceptionOnInvalidReferenceBehaviorPass.phpOI <20>_OV<>z<89><7A>Mvendor/symfony/dependency-injection/Compiler/DefinitionErrorExceptionPass.phpNI <20>_N}I!<21>Cvendor/symfony/dependency-injection/Compiler/ResolveHotPathPass.phpT I <20>_T <00><>*D<>Avendor/symfony/dependency-injection/Compiler/ResolveClassPass.phpI <20>_<00><><F9><14>Mvendor/symfony/dependency-injection/Compiler/RegisterEnvVarProcessorsPass.php<68> I <20>_<D9> z<>Lvendor/symfony/dependency-injection/Compiler/AutowireRequiredMethodsPass.phpI <20>_<00>sn<>Lvendor/symfony/dependency-injection/Compiler/CheckDefinitionValidityPass.phpMI <20>_M<00><><B1><FF><BC>Ovendor/symfony/dependency-injection/Compiler/RegisterServiceSubscribersPass.php_I <20>__<00> <16>Fvendor/symfony/dependency-injection/Compiler/AbstractRecursivePass.php> I <20>_> <00>8<1A><>9vendor/symfony/dependency-injection/Compiler/Compiler.php<68> I <20>_<D9> <00><><E5>(<28>Ovendor/symfony/dependency-injection/Compiler/ResolveReferencesToAliasesPass.php<68> I <20>_<D9> <00><>Fvendor/symfony/dependency-injection/Compiler/ServiceLocatorTagPass.php<68>I <20>_<D9><00>:<3A> <0B>Pvendor/symfony/dependency-injection/Compiler/MergeExtensionConfigurationPass.php<68>!I <20>_<D9>!<15><><A4><F5>Ivendor/symfony/dependency-injection/Compiler/RemovePrivateAliasesPass.phpZI <20>_Z<00>\<5C><><D6>Lvendor/symfony/dependency-injection/Compiler/CheckCircularReferencesPass.php<68> I <20>_<D9> $<14>ŤEvendor/symfony/dependency-injection/Compiler/ResolveNoPreloadPass.php I <20>_ <00><><B8>><3E>Jvendor/symfony/dependency-injection/Compiler/ServiceReferenceGraphEdge.php<68>I <20>_<D9><00><>q<>=vendor/symfony/dependency-injection/Compiler/AutowirePass.phpIII <20>_IIp&<26><14>Kvendor/symfony/dependency-injection/Compiler/CheckArgumentsValidityPass.php<68> I <20>_<D9> <00><>|F<>Fvendor/symfony/dependency-injection/Compiler/ServiceReferenceGraph.php
I <20>_
<00><>Dvendor/symfony/dependency-injection/Compiler/ResolveBindingsPass.php<68>"I <20>_<D9>" <09><>d<94>Hvendor/symfony/dependency-injection/Compiler/ResolveFactoryClassPass.php<68>I <20>_<D9>Ӊ<11><>;vendor/symfony/dependency-injection/Compiler/PassConfig.phpDI <20>_Dמ<D79E><7F>Nvendor/symfony/dependency-injection/Compiler/ResolveServiceSubscribersPass.phpeI <20>_e<00>;/<2F><>Jvendor/symfony/dependency-injection/Compiler/ServiceReferenceGraphNode.phpW I <20>_W FZ<46><19>Fvendor/symfony/dependency-injection/Compiler/ExtensionCompilerPass.php|I <20>_|<><7F>ƤDvendor/symfony/dependency-injection/Compiler/ResolvePrivatesPass.php<68>I <20>_<D9>Tĉ7<C489>Svendor/symfony/dependency-injection/Compiler/ReplaceAliasByActualDefinitionPass.phpq I <20>_q "7<><37><99>Lvendor/symfony/dependency-injection/Compiler/RemoveUnusedDefinitionsPass.php, I <20>_, <00><1D>ɤMvendor/symfony/dependency-injection/Compiler/ResolveInvalidReferencesPass.php<68>I <20>_<D9>^<>ʤNvendor/symfony/dependency-injection/Compiler/RemoveAbstractDefinitionsPass.php<68>I <20>_<D9>y$<24>f<A9>Jvendor/symfony/dependency-injection/Compiler/ResolveDecoratorStackPass.php]I <20>_]ʶ<01><>Kvendor/symfony/dependency-injection/Compiler/PriorityTaggedServiceTrait.php<68>I <20>_<D9>ܖ<>S<B3>Rvendor/symfony/dependency-injection/Compiler/ResolveTaggedIteratorArgumentPass.php<68>I <20>_<D9><00>vu<76><75>Fvendor/symfony/dependency-injection/Compiler/CompilerPassInterface.php<68>I <20>_<D9>B<><42>_<CE>Kvendor/symfony/dependency-injection/Compiler/CheckReferenceValidityPass.php<68>I <20>_<D9><00><>R%<25>Evendor/symfony/dependency-injection/Compiler/AutoAliasServicePass.php^I <20>_^<04> <20>Ovendor/symfony/dependency-injection/Compiler/AutowireRequiredPropertiesPass.php<68>I <20>_<D9>\<5C>Z<EA><5A>Lvendor/symfony/dependency-injection/Compiler/ValidateEnvPlaceholdersPass.php-I <20>_-ϛ<>?<3F>Mvendor/symfony/dependency-injection/Compiler/AnalyzeServiceReferencesPass.php<68>I <20>_<D9><00><>-<2D><>Mvendor/symfony/dependency-injection/Compiler/InlineServiceDefinitionsPass.phpI <20>_<00>@<40>r<C8>Mvendor/symfony/dependency-injection/Compiler/RegisterReverseContainerPass.php<68>I <20>_<D9>$<24>;<3B><>Rvendor/symfony/dependency-injection/Compiler/ResolveInstanceofConditionalsPass.php I <20>_ `e6<65>Lvendor/symfony/dependency-injection/Compiler/ResolveChildDefinitionsPass.php6I <20>_6<00>s <0C><>Qvendor/symfony/dependency-injection/Compiler/ResolveParameterPlaceHoldersPass.php<68> I <20>_<D9> W<>g!<21>Rvendor/symfony/dependency-injection/Compiler/AliasDeprecatedPublicServicesPass.php<68> I <20>_<D9> <00><> <16>Kvendor/symfony/dependency-injection/Compiler/ResolveEnvPlaceholdersPass.phpMI <20>_M<00><><FF><D0><D2>Jvendor/symfony/dependency-injection/Compiler/ResolveNamedArgumentsPass.php<68>I <20>_<D9>ń<>y<81>Jvendor/symfony/dependency-injection/Compiler/CheckTypeDeclarationsPass.php-I <20>_-}H<>w<B8>Evendor/symfony/dependency-injection/Compiler/DecoratorServicePass.phpYI <20>_Y<00>A;<3B><>@vendor/symfony/dependency-injection/EnvVarProcessorInterface.php<68>I <20>_<D9><00><><BD><D0><F4>@vendor/symfony/dependency-injection/TaggedContainerInterface.php<68>I <20>_<D9><00>A<84><41>Tvendor/symfony/dependency-injection/LazyProxy/Instantiator/InstantiatorInterface.phpMI <20>_M@#<23><><DF>Vvendor/symfony/dependency-injection/LazyProxy/Instantiator/RealServiceInstantiator.phpiI <20>_i<00>Fvendor/symfony/dependency-injection/LazyProxy/PhpDumper/NullDumper.php<68>I <20>_<D9><00>?Q<>Kvendor/symfony/dependency-injection/LazyProxy/PhpDumper/DumperInterface.php{I <20>_{<00><0F><>=vendor/symfony/dependency-injection/LazyProxy/ProxyHelper.php<68>I <20>_<D9><00><>.t<>2vendor/symfony/dependency-injection/Definition.phpjXI <20>_jX<1E><><C7><D9>Bvendor/symfony/dependency-injection/ExpressionLanguageProvider.php<68>I <20>_<D9><00><17>?<3F>7vendor/symfony/dependency-injection/EnvVarProcessor.php6(I <20>_6( AI<>0vendor/symfony/dependency-injection/Variable.php<68>I <20>_<D9>pn<70>ʤ5vendor/symfony/dependency-injection/Dumper/Dumper.php<68>I <20>_<D9>2<>gU<67>8vendor/symfony/dependency-injection/Dumper/Preloader.phpI <20>_[j<><06>=vendor/symfony/dependency-injection/Dumper/GraphvizDumper.php<68>$I <20>_<D9>$<00><>m|<7C>9vendor/symfony/dependency-injection/Dumper/YamlDumper.phpl3I <20>_l3<00>׾a<D7BE>>vendor/symfony/dependency-injection/Dumper/DumperInterface.php<68>I <20>_<D9><00><>ٵ<9E>8vendor/symfony/dependency-injection/Dumper/PhpDumper.phpp_I <20>_p_ZA<5A>Ǥ8vendor/symfony/dependency-injection/Dumper/XmlDumper.phpj>I <20>_j>h<>$<24>?vendor/symfony/dependency-injection/ContainerAwareInterface.phpNI <20>_N<00><>GX<47>1vendor/symfony/deprecation-contracts/function.php<68>I <20>_<D9>rg<72><67><F2>,vendor/symfony/deprecation-contracts/LICENSE$I <20>_$LO!
<A4>'vendor/symfony/event-dispatcher/LICENSE)I <20>_)=<3D><1C><><vendor/symfony/event-dispatcher/EventDispatcherInterface.php<68> I <20>_<D9> <1D><>Τ3vendor/symfony/event-dispatcher/EventDispatcher.php<68>#I <20>_<D9>#<11>S<08>0vendor/symfony/event-dispatcher/GenericEvent.phpI <20>_<00> /a<><vendor/symfony/event-dispatcher/ImmutableEventDispatcher.php<68>I <20>_<D9><00><>-<2D>>vendor/symfony/event-dispatcher/LegacyEventDispatcherProxy.phprI <20>_r<00>󭷤<vendor/symfony/event-dispatcher/EventSubscriberInterface.php<68>I <20>_<D9><00><05><><96>9vendor/symfony/event-dispatcher/Debug/WrappedListener.php<68>I <20>_<D9><00>6<D7>k<89>Bvendor/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php<68>,I <20>_<D9>,߂<1C>Mvendor/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php<68>"I <20>_<D9>"{GkQ<6B>Kvendor/symfony/event-dispatcher/DependencyInjection/AddEventAliasesPass.phpI <20>__<><5F><9D>1vendor/symfony/event-dispatcher-contracts/LICENSE)I <20>_)i8<69>z<86>Fvendor/symfony/event-dispatcher-contracts/EventDispatcherInterface.php<68>I <20>_<D9><18><>ڤ3vendor/symfony/event-dispatcher-contracts/Event.php<68>I <20>_<D9><00><><17><>!vendor/symfony/filesystem/LICENSE)I <20>_)=<3D><1C><>:vendor/symfony/filesystem/Exception/ExceptionInterface.php<68>I <20>_<D9> n<>j<DF><vendor/symfony/filesystem/Exception/IOExceptionInterface.php<68>I <20>_<D9><00><>i<99><69>3vendor/symfony/filesystem/Exception/IOException.php<68>I <20>_<D9>HD<><44>@vendor/symfony/filesystem/Exception/InvalidArgumentException.php<68>I <20>_<D9><00>*<2A><><93>=vendor/symfony/filesystem/Exception/FileNotFoundException.php<68>I <20>_<D9> Fʹ<46>(vendor/symfony/filesystem/Filesystem.php<68>oI <20>_<D9>o=$C <0A>vendor/symfony/finder/LICENSE)I <20>_)=<3D><1C><>>vendor/symfony/finder/Exception/DirectoryNotFoundException.php<68>I <20>_<D9><00>RI<>9vendor/symfony/finder/Exception/AccessDeniedException.php<68>I <20>_<D9><00>cWޤvendor/symfony/finder/Glob.php$I <20>_$<00>t<E0>ڤ vendor/symfony/finder/Finder.php}WI <20>_}W<><EF8794>%vendor/symfony/finder/SplFileInfo.phpI <20>_<00>b<B9><0F>3vendor/symfony/finder/Iterator/SortableIterator.php<68>I <20>_<D9><00><><B3>t<99>;vendor/symfony/finder/Iterator/DepthRangeFilterIterator.php<68>I <20>_<D9><00><><B8>դ:vendor/symfony/finder/Iterator/SizeRangeFilterIterator.php<68>I <20>_<D9><03>=vendor/symfony/finder/Iterator/RecursiveDirectoryIterator.phpQI <20>_Q|@u<>9vendor/symfony/finder/Iterator/FileTypeFilterIterator.phpCI <20>_CQN<51><4E><B6>7vendor/symfony/finder/Iterator/CustomFilterIterator.php<68>I <20>_<D9>d)<29>9vendor/symfony/finder/Iterator/FilenameFilterIterator.php<68>I <20>_<D9><00><>4 <0C><vendor/symfony/finder/Iterator/FilecontentFilterIterator.php<68>I <20>_<D9><00><><9C>,<2C>:vendor/symfony/finder/Iterator/DateRangeFilterIterator.php<68>I <20>_<D9><00><><83>)<29>5vendor/symfony/finder/Iterator/PathFilterIterator.php<68>I <20>_<D9>2<><32>Avendor/symfony/finder/Iterator/ExcludeDirectoryFilterIterator.php<68> I <20>_<D9> <00><>za<7A>=vendor/symfony/finder/Iterator/MultiplePcreFilterIterator.php<68> I <20>_<D9> <00><><B8>g<BD>5vendor/symfony/finder/Comparator/NumberComparator.php
I <20>_
<0F><><B9>3vendor/symfony/finder/Comparator/DateComparator.php<68>I <20>_<D9><00>is<69>/vendor/symfony/finder/Comparator/Comparator.php}I <20>_}u<><75><C1><A7>#vendor/symfony/finder/Gitignore.php<68>I <20>_<D9><00>\<5C>ɤ+vendor/symfony/polyfill-ctype/bootstrap.phpI <20>_<00>01<30><31>%vendor/symfony/polyfill-ctype/LICENSE)I <20>_)<00>`e0<65>'vendor/symfony/polyfill-ctype/Ctype.php}I <20>_}\<5C>3vendor/symfony/polyfill-intl-grapheme/bootstrap.php<68>I <20>_<D9><00><>ٕ<F9>-vendor/symfony/polyfill-intl-grapheme/LICENSE)I <20>_)<1F>\<5C><>2vendor/symfony/polyfill-intl-grapheme/Grapheme.php<68>I <20>_<D9><00>g@<40><>5vendor/symfony/polyfill-intl-normalizer/bootstrap.phpyI <20>_yu<><75><E9><94>/vendor/symfony/polyfill-intl-normalizer/LICENSE)I <20>_)<1F>\<5C><>6vendor/symfony/polyfill-intl-normalizer/Normalizer.php<68>$I <20>_<D9>$<00>e<D3>"<22>Fvendor/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.phpQI <20>_Q<00>ݮ¤Lvendor/symfony/polyfill-intl-normalizer/Resources/unidata/combiningClass.phpD5I <20>_D5<00><><8D> <0B>Rvendor/symfony/polyfill-intl-normalizer/Resources/unidata/canonicalComposition.php<68>DI <20>_<D9>D'<27>Tvendor/symfony/polyfill-intl-normalizer/Resources/unidata/canonicalDecomposition.php{<7B>I <20>_{<7B><00>je<6A><65>Xvendor/symfony/polyfill-intl-normalizer/Resources/unidata/compatibilityDecomposition.phpoI <20>_oc<>,<2C><>.vendor/symfony/polyfill-mbstring/bootstrap.php<68>I <20>_<D9>k<><C5><7F>(vendor/symfony/polyfill-mbstring/LICENSE)I <20>_)<1F>\<5C><>-vendor/symfony/polyfill-mbstring/Mbstring.php<68>mI <20>_<D9>m*T<><54><A9>@vendor/symfony/polyfill-mbstring/Resources/unidata/upperCase.php<68>`I <20>_<D9>`<00>S<8A><53><E9>@vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php<68>_I <20>_<D9>_Z<><5A><B6><9F>Fvendor/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php9I <20>_9>|zK<7A>+vendor/symfony/polyfill-php73/bootstrap.php<68>I <20>_<D9><00>S&Q<>%vendor/symfony/polyfill-php73/LICENSE)I <20>_)<00>`e0<65>'vendor/symfony/polyfill-php73/Php73.phpgI <20>_g/<2F><>n<C6>?vendor/symfony/polyfill-php73/Resources/stubs/JsonException.phpI <20>_<F<><46>+vendor/symfony/polyfill-php80/bootstrap.php<68>I <20>_<D9>d%<25>R<9E>%vendor/symfony/polyfill-php80/LICENSE$I <20>_$LO!
<A4><vendor/symfony/polyfill-php80/Resources/stubs/Stringable.php<68>I <20>_<D9><00><11>9<FE>;vendor/symfony/polyfill-php80/Resources/stubs/Attribute.php<68>I <20>_<D9>L<>_<FB><5F>Evendor/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php3I <20>_3y4%<1B><vendor/symfony/polyfill-php80/Resources/stubs/ValueError.php*I <20>_*<00><><A5>*<2A>'vendor/symfony/polyfill-php80/Php80.php- I <20>_- <00><05>5<E2>7vendor/symfony/service-contracts/Attribute/Required.php<68>I <20>_<D9><00><><D7> <20>8vendor/symfony/service-contracts/ServiceLocatorTrait.php<68>I <20>_<D9><00>'<27>e<C7>?vendor/symfony/service-contracts/ServiceSubscriberInterface.php<68>I <20>_<D9>SR<53>ܤ(vendor/symfony/service-contracts/LICENSE)I <20>_)i8<69>z<86>=vendor/symfony/service-contracts/ServiceProviderInterface.php<68>I <20>_<D9><00>mX¤<vendor/symfony/service-contracts/Test/ServiceLocatorTest.phpO I <20>_O <00>!<21><><93>3vendor/symfony/service-contracts/ResetInterface.php<68>I <20>_<D9><00>v<17><>;vendor/symfony/service-contracts/ServiceSubscriberTrait.phpI <20>_<00><>#O<>3vendor/symfony/string/Inflector/FrenchInflector.php<68>I <20>_<D9>yxt<78>6vendor/symfony/string/Inflector/InflectorInterface.php<68>I <20>_<D9>7<>6<BE><36>4vendor/symfony/string/Inflector/EnglishInflector.phpg:I <20>_g:<00><>.<2E><>vendor/symfony/string/LICENSE)I <20>_)a<><61><19>/vendor/symfony/string/AbstractUnicodeString.php<68>fI <20>_<D9>f0]<5D>"<22>6vendor/symfony/string/Exception/ExceptionInterface.phpQI <20>_Q$<24><>դ4vendor/symfony/string/Exception/RuntimeException.phppI <20>_p<00><><vendor/symfony/string/Exception/InvalidArgumentException.php<68>I <20>_<D9>e<><65><AF><AF>'vendor/symfony/string/UnicodeString.php<68>1I <20>_<D9>1<00><>`><3E>(vendor/symfony/string/AbstractString.php<68>GI <20>_<D9>G<00>"<22>g<F1>.vendor/symfony/string/Slugger/AsciiSlugger.php<68>I <20>_<D9><00><>0=<3D>2vendor/symfony/string/Slugger/SluggerInterface.php<68>I <20>_<D9>"<22>O<CC><4F>$vendor/symfony/string/ByteString.php<68>;I <20>_<D9>;<00>M-֤<vendor/symfony/string/Resources/data/wcswidth_table_zero.phpK7I <20>_K7ϔ<><CF94><AF><vendor/symfony/string/Resources/data/wcswidth_table_wide.php<68>/I <20>_<D9>/<00><>c<BD><63>-vendor/symfony/string/Resources/functions.php<68>I <20>_<D9><00><>i5<69>)vendor/symfony/string/CodePointString.php{I <20>_{n^t<1A>$vendor/symfony/string/LazyString.php)I <20>_)<00>I!<21>+vendor/symfony/yaml/Command/LintCommand.php<68> I <20>_<D9> <00>.<2E><04>vendor/symfony/yaml/Parser.php<68><70>I <20>_<D9><5F><00>kU<6B><55>vendor/symfony/yaml/Yaml.php<68> I <20>_<D9> 3JU<4A>vendor/symfony/yaml/LICENSE)I <20>_)=<3D><1C><>4vendor/symfony/yaml/Exception/ExceptionInterface.php<68>I <20>_<D9>B9<07><>2vendor/symfony/yaml/Exception/RuntimeException.php<68>I <20>_<D9><00>_q<5F><71>0vendor/symfony/yaml/Exception/ParseException.php<68> I <20>_<D9> <00>#:8<>/vendor/symfony/yaml/Exception/DumpException.php<68>I <20>_<D9><08><15>vendor/symfony/yaml/Dumper.phpRI <20>_R<00><><C3>H<E0>vendor/symfony/yaml/Inline.phpI <20>_sb.Ф+vendor/symfony/yaml/Resources/bin/yaml-lintyI <20>_yh<>5<89><35>!vendor/symfony/yaml/Unescaper.php"I <20>_"԰<14><>vendor/symfony/yaml/Escaper.php<68>I <20>_<D9><00>ʭ<1E>'vendor/symfony/yaml/Tag/TaggedValue.php<68>I <20>_<D9>n<><6E>%<25>services:
tokenize_command:
class: Helmich\TypoScriptLint\Command\TokenizeCommand
calls:
- [injectTokenizer, ['@tokenizer']]
- [injectTokenPrinter, ['@token_printer_structured']]
lint_command:
class: Helmich\TypoScriptLint\Command\LintCommand
public: true
arguments:
- '@linter'
- '@linter_configuration_locator'
- '@logger_builder'
- '@finder'
- '@dispatcher'
parse_command:
class: Helmich\TypoScriptLint\Command\ParseCommand
calls:
- [injectParser, ['@parser']]
linter:
class: Helmich\TypoScriptLint\Linter\Linter
arguments: ['@tokenizer', '@parser', '@sniff_locator']
linter_configuration:
class: Helmich\TypoScriptLint\Linter\LinterConfiguration
linter_configuration_locator:
class: Helmich\TypoScriptLint\Linter\Configuration\ConfigurationLocator
arguments: ['@config_loader', '@config_processor']
logger_builder:
class: Helmich\TypoScriptLint\Logging\LinterLoggerBuilder
sniff_locator:
class: Helmich\TypoScriptLint\Linter\Sniff\SniffLocator
# Configuration management
config_locator:
class: Symfony\Component\Config\FileLocator
arguments:
- ['%dir.cwd%', '%dir.typoscriptlint_root%']
config_yaml_loader:
class: Helmich\TypoScriptLint\Linter\Configuration\YamlConfigurationLoader
arguments: ['@config_locator', '@yaml_parser', '@filesystem']
config_loader_resolver:
class: Symfony\Component\Config\Loader\LoaderResolver
arguments:
- ['@config_yaml_loader']
config_loader:
class: Symfony\Component\Config\Loader\DelegatingLoader
arguments: ['@config_loader_resolver']
config_processor:
class: Symfony\Component\Config\Definition\Processor
yaml_parser:
class: Symfony\Component\Yaml\Parser
# Tool classes
filesystem:
class: Helmich\TypoScriptLint\Util\Filesystem
finder:
class: Helmich\TypoScriptLint\Util\Finder
arguments: ['@symfony_finder', '@filesystem']
symfony_finder:
class: Symfony\Component\Finder\Finder
shared: false
dispatcher:
synthetic: true
MIT License
Copyright (c) 2018 Martin Helmich
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
sniffs:
- class: Indentation
parameters:
useSpaces: true
indentPerLevel: 4
indentConditions: false
- class: DeadCode
- class: OperatorWhitespace
- class: RepeatingRValue
parameters:
allowedRightValues:
- "TYPO3\\CMS\\Frontend\\DataProcessing\\DatabaseQueryProcessor"
- class: DuplicateAssignment
- class: EmptySection
- class: NestingConsistency
parameters:
commonPathPrefixThreshold: 1
<?php
/*
* This file is part of tsparse.
* https://github.com/martin-helmich/ts-parse
*
* (C) 2014 Martin Helmich <kontakt@martin-helmich.de>
*
* For license information, view the LICENSE.md file.
*/
use Helmich\TypoScriptLint\Application;
use Helmich\TypoScriptParser\TypoScriptParserExtension;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
use Symfony\Component\EventDispatcher\EventDispatcher;
if (file_exists(__DIR__ . '/vendor/autoload.php')) {
require_once __DIR__ . '/vendor/autoload.php';
} else if (file_exists(__DIR__ . '/../../autoload.php')) {
/** @noinspection PhpIncludeInspection */
require_once __DIR__ . '/../../autoload.php';
} else {
die('Could not find an autoload.php. Did you set up all dependencies?');
}
$dispatcher = new EventDispatcher();
$container = new ContainerBuilder();
$container->setParameter('dir.cwd', getcwd());
$container->setParameter('dir.tslint_root', __DIR__);
$container->setParameter('dir.typoscriptlint_root', __DIR__);
$container->registerExtension(new TypoScriptParserExtension());
$loader = new YamlFileLoader($container, new FileLocator(__DIR__));
$loader->load('services.yml');
$container->set('dispatcher', $dispatcher);
$container->loadFromExtension('typoscript_parser');
$container->compile();
$application = new Application($container);
$application->setDispatcher($dispatcher);
$application->run();{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "54487b46252304e884e1abdd1f162494",
"packages": [
{
"name": "helmich/typo3-typoscript-parser",
"version": "v2.1.4",
"source": {
"type": "git",
"url": "https://github.com/martin-helmich/typo3-typoscript-parser.git",
"reference": "eedd34773442722e114b9072afcb585110c5e8bf"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/martin-helmich/typo3-typoscript-parser/zipball/eedd34773442722e114b9072afcb585110c5e8bf",
"reference": "eedd34773442722e114b9072afcb585110c5e8bf",
"shasum": ""
},
"require": {
"php": ">=7.2",
"symfony/config": "~3.0|~4.0|~5.0",
"symfony/dependency-injection": "~3.0|~4.0|~5.0",
"symfony/yaml": "~3.0|~4.0|~5.0"
},
"require-dev": {
"php-vfs/php-vfs": "^1.3",
"phpunit/phpunit": "^8.0",
"symfony/phpunit-bridge": "~2.7|~3.0|~4.0|~5.0",
"vimeo/psalm": "^3.7"
},
"type": "library",
"autoload": {
"psr-4": {
"Helmich\\TypoScriptParser\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Martin Helmich",
"email": "m.helmich@mittwald.de",
"role": "lead"
}
],
"description": "Parser for the TYPO3 configuration language TypoScript.",
"homepage": "https://github.com/martin-helmich",
"support": {
"issues": "https://github.com/martin-helmich/typo3-typoscript-parser/issues",
"source": "https://github.com/martin-helmich/typo3-typoscript-parser/tree/master"
},
"time": "2020-01-02T13:07:43+00:00"
},
{
"name": "psr/container",
"version": "1.0.0",
"source": {
"type": "git",
"url": "https://github.com/php-fig/container.git",
"reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f",
"reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\Container\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "http://www.php-fig.org/"
}
],
"description": "Common Container Interface (PHP FIG PSR-11)",
"homepage": "https://github.com/php-fig/container",
"keywords": [
"PSR-11",
"container",
"container-interface",
"container-interop",
"psr"
],
"support": {
"issues": "https://github.com/php-fig/container/issues",
"source": "https://github.com/php-fig/container/tree/master"
},
"time": "2017-02-14T16:28:37+00:00"
},
{
"name": "psr/event-dispatcher",
"version": "1.0.0",
"source": {
"type": "git",
"url": "https://github.com/php-fig/event-dispatcher.git",
"reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0",
"reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0",
"shasum": ""
},
"require": {
"php": ">=7.2.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\EventDispatcher\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "http://www.php-fig.org/"
}
],
"description": "Standard interfaces for event handling.",
"keywords": [
"events",
"psr",
"psr-14"
],
"support": {
"issues": "https://github.com/php-fig/event-dispatcher/issues",
"source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0"
},
"time": "2019-01-08T18:20:26+00:00"
},
{
"name": "symfony/config",
"version": "v5.2.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/config.git",
"reference": "fa1219ecbf96bb5db59f2599cba0960a0d9c3aea"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/config/zipball/fa1219ecbf96bb5db59f2599cba0960a0d9c3aea",
"reference": "fa1219ecbf96bb5db59f2599cba0960a0d9c3aea",
"shasum": ""
},
"require": {
"php": ">=7.2.5",
"symfony/deprecation-contracts": "^2.1",
"symfony/filesystem": "^4.4|^5.0",
"symfony/polyfill-ctype": "~1.8",
"symfony/polyfill-php80": "^1.15"
},
"conflict": {
"symfony/finder": "<4.4"
},
"require-dev": {
"symfony/event-dispatcher": "^4.4|^5.0",
"symfony/finder": "^4.4|^5.0",
"symfony/messenger": "^4.4|^5.0",
"symfony/service-contracts": "^1.1|^2",
"symfony/yaml": "^4.4|^5.0"
},
"suggest": {
"symfony/yaml": "To use the yaml reference dumper"
},
"type": "library",
"autoload": {
"psr-4": {
"Symfony\\Component\\Config\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony Config Component",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/config/tree/v5.2.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2020-11-16T18:02:40+00:00"
},
{
"name": "symfony/console",
"version": "v5.2.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
"reference": "3e0564fb08d44a98bd5f1960204c958e57bd586b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/3e0564fb08d44a98bd5f1960204c958e57bd586b",
"reference": "3e0564fb08d44a98bd5f1960204c958e57bd586b",
"shasum": ""
},
"require": {
"php": ">=7.2.5",
"symfony/polyfill-mbstring": "~1.0",
"symfony/polyfill-php73": "^1.8",
"symfony/polyfill-php80": "^1.15",
"symfony/service-contracts": "^1.1|^2",
"symfony/string": "^5.1"
},
"conflict": {
"symfony/dependency-injection": "<4.4",
"symfony/dotenv": "<5.1",
"symfony/event-dispatcher": "<4.4",
"symfony/lock": "<4.4",
"symfony/process": "<4.4"
},
"provide": {
"psr/log-implementation": "1.0"
},
"require-dev": {
"psr/log": "~1.0",
"symfony/config": "^4.4|^5.0",
"symfony/dependency-injection": "^4.4|^5.0",
"symfony/event-dispatcher": "^4.4|^5.0",
"symfony/lock": "^4.4|^5.0",
"symfony/process": "^4.4|^5.0",
"symfony/var-dumper": "^4.4|^5.0"
},
"suggest": {
"psr/log": "For using the console logger",
"symfony/event-dispatcher": "",
"symfony/lock": "",
"symfony/process": ""
},
"type": "library",
"autoload": {
"psr-4": {
"Symfony\\Component\\Console\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony Console Component",
"homepage": "https://symfony.com",
"keywords": [
"cli",
"command line",
"console",
"terminal"
],
"support": {
"source": "https://github.com/symfony/console/tree/v5.2.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2020-11-28T11:24:18+00:00"
},
{
"name": "symfony/dependency-injection",
"version": "v5.2.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/dependency-injection.git",
"reference": "98cec9b9f410a4832e239949a41d47182862c3a4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/dependency-injection/zipball/98cec9b9f410a4832e239949a41d47182862c3a4",
"reference": "98cec9b9f410a4832e239949a41d47182862c3a4",
"shasum": ""
},
"require": {
"php": ">=7.2.5",
"psr/container": "^1.0",
"symfony/deprecation-contracts": "^2.1",
"symfony/polyfill-php80": "^1.15",
"symfony/service-contracts": "^1.1.6|^2"
},
"conflict": {
"symfony/config": "<5.1",
"symfony/finder": "<4.4",
"symfony/proxy-manager-bridge": "<4.4",
"symfony/yaml": "<4.4"
},
"provide": {
"psr/container-implementation": "1.0",
"symfony/service-implementation": "1.0"
},
"require-dev": {
"symfony/config": "^5.1",
"symfony/expression-language": "^4.4|^5.0",
"symfony/yaml": "^4.4|^5.0"
},
"suggest": {
"symfony/config": "",
"symfony/expression-language": "For using expressions in service container configuration",
"symfony/finder": "For using double-star glob patterns or when GLOB_BRACE portability is required",
"symfony/proxy-manager-bridge": "Generate service proxies to lazy load them",
"symfony/yaml": ""
},
"type": "library",
"autoload": {
"psr-4": {
"Symfony\\Component\\DependencyInjection\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony DependencyInjection Component",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/dependency-injection/tree/v5.2.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2020-11-28T11:24:18+00:00"
},
{
"name": "symfony/deprecation-contracts",
"version": "v2.2.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/deprecation-contracts.git",
"reference": "5fa56b4074d1ae755beb55617ddafe6f5d78f665"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/5fa56b4074d1ae755beb55617ddafe6f5d78f665",
"reference": "5fa56b4074d1ae755beb55617ddafe6f5d78f665",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.2-dev"
},
"thanks": {
"name": "symfony/contracts",
"url": "https://github.com/symfony/contracts"
}
},
"autoload": {
"files": [
"function.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "A generic function and convention to trigger deprecation notices",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/deprecation-contracts/tree/master"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2020-09-07T11:33:47+00:00"
},
{
"name": "symfony/event-dispatcher",
"version": "v5.2.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/event-dispatcher.git",
"reference": "aa13a09811e6d2ad43f8fb336bebdb7691d85d3c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/aa13a09811e6d2ad43f8fb336bebdb7691d85d3c",
"reference": "aa13a09811e6d2ad43f8fb336bebdb7691d85d3c",
"shasum": ""
},
"require": {
"php": ">=7.2.5",
"symfony/deprecation-contracts": "^2.1",
"symfony/event-dispatcher-contracts": "^2",
"symfony/polyfill-php80": "^1.15"
},
"conflict": {
"symfony/dependency-injection": "<4.4"
},
"provide": {
"psr/event-dispatcher-implementation": "1.0",
"symfony/event-dispatcher-implementation": "2.0"
},
"require-dev": {
"psr/log": "~1.0",
"symfony/config": "^4.4|^5.0",
"symfony/dependency-injection": "^4.4|^5.0",
"symfony/error-handler": "^4.4|^5.0",
"symfony/expression-language": "^4.4|^5.0",
"symfony/http-foundation": "^4.4|^5.0",
"symfony/service-contracts": "^1.1|^2",
"symfony/stopwatch": "^4.4|^5.0"
},
"suggest": {
"symfony/dependency-injection": "",
"symfony/http-kernel": ""
},
"type": "library",
"autoload": {
"psr-4": {
"Symfony\\Component\\EventDispatcher\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony EventDispatcher Component",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/event-dispatcher/tree/v5.2.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2020-11-01T16:14:45+00:00"
},
{
"name": "symfony/event-dispatcher-contracts",
"version": "v2.2.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/event-dispatcher-contracts.git",
"reference": "0ba7d54483095a198fa51781bc608d17e84dffa2"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/0ba7d54483095a198fa51781bc608d17e84dffa2",
"reference": "0ba7d54483095a198fa51781bc608d17e84dffa2",
"shasum": ""
},
"require": {
"php": ">=7.2.5",
"psr/event-dispatcher": "^1"
},
"suggest": {
"symfony/event-dispatcher-implementation": ""
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.2-dev"
},
"thanks": {
"name": "symfony/contracts",
"url": "https://github.com/symfony/contracts"
}
},
"autoload": {
"psr-4": {
"Symfony\\Contracts\\EventDispatcher\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Generic abstractions related to dispatching event",
"homepage": "https://symfony.com",
"keywords": [
"abstractions",
"contracts",
"decoupling",
"interfaces",
"interoperability",
"standards"
],
"support": {
"source": "https://github.com/symfony/event-dispatcher-contracts/tree/v2.2.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2020-09-07T11:33:47+00:00"
},
{
"name": "symfony/filesystem",
"version": "v5.2.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/filesystem.git",
"reference": "bb92ba7f38b037e531908590a858a04d85c0e238"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/filesystem/zipball/bb92ba7f38b037e531908590a858a04d85c0e238",
"reference": "bb92ba7f38b037e531908590a858a04d85c0e238",
"shasum": ""
},
"require": {
"php": ">=7.2.5",
"symfony/polyfill-ctype": "~1.8"
},
"type": "library",
"autoload": {
"psr-4": {
"Symfony\\Component\\Filesystem\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony Filesystem Component",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/filesystem/tree/v5.2.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2020-11-12T09:58:18+00:00"
},
{
"name": "symfony/finder",
"version": "v5.2.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/finder.git",
"reference": "fd8305521692f27eae3263895d1ef1571c71a78d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/finder/zipball/fd8305521692f27eae3263895d1ef1571c71a78d",
"reference": "fd8305521692f27eae3263895d1ef1571c71a78d",
"shasum": ""
},
"require": {
"php": ">=7.2.5"
},
"type": "library",
"autoload": {
"psr-4": {
"Symfony\\Component\\Finder\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony Finder Component",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/finder/tree/v5.2.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2020-11-18T09:42:36+00:00"
},
{
"name": "symfony/polyfill-ctype",
"version": "v1.20.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-ctype.git",
"reference": "f4ba089a5b6366e453971d3aad5fe8e897b37f41"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/f4ba089a5b6366e453971d3aad5fe8e897b37f41",
"reference": "f4ba089a5b6366e453971d3aad5fe8e897b37f41",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"suggest": {
"ext-ctype": "For best performance"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.20-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"autoload": {
"psr-4": {
"Symfony\\Polyfill\\Ctype\\": ""
},
"files": [
"bootstrap.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Gert de Pagter",
"email": "BackEndTea@gmail.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for ctype functions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"ctype",
"polyfill",
"portable"
],
"support": {
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.20.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2020-10-23T14:02:19+00:00"
},
{
"name": "symfony/polyfill-intl-grapheme",
"version": "v1.20.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-intl-grapheme.git",
"reference": "c7cf3f858ec7d70b89559d6e6eb1f7c2517d479c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/c7cf3f858ec7d70b89559d6e6eb1f7c2517d479c",
"reference": "c7cf3f858ec7d70b89559d6e6eb1f7c2517d479c",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"suggest": {
"ext-intl": "For best performance"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.20-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"autoload": {
"psr-4": {
"Symfony\\Polyfill\\Intl\\Grapheme\\": ""
},
"files": [
"bootstrap.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for intl's grapheme_* functions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"grapheme",
"intl",
"polyfill",
"portable",
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.20.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2020-10-23T14:02:19+00:00"
},
{
"name": "symfony/polyfill-intl-normalizer",
"version": "v1.20.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-intl-normalizer.git",
"reference": "727d1096295d807c309fb01a851577302394c897"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/727d1096295d807c309fb01a851577302394c897",
"reference": "727d1096295d807c309fb01a851577302394c897",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"suggest": {
"ext-intl": "For best performance"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.20-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"autoload": {
"psr-4": {
"Symfony\\Polyfill\\Intl\\Normalizer\\": ""
},
"files": [
"bootstrap.php"
],
"classmap": [
"Resources/stubs"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for intl's Normalizer class and related functions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"intl",
"normalizer",
"polyfill",
"portable",
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.20.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2020-10-23T14:02:19+00:00"
},
{
"name": "symfony/polyfill-mbstring",
"version": "v1.20.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
"reference": "39d483bdf39be819deabf04ec872eb0b2410b531"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/39d483bdf39be819deabf04ec872eb0b2410b531",
"reference": "39d483bdf39be819deabf04ec872eb0b2410b531",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"suggest": {
"ext-mbstring": "For best performance"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.20-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"autoload": {
"psr-4": {
"Symfony\\Polyfill\\Mbstring\\": ""
},
"files": [
"bootstrap.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for the Mbstring extension",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"mbstring",
"polyfill",
"portable",
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.20.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2020-10-23T14:02:19+00:00"
},
{
"name": "symfony/polyfill-php73",
"version": "v1.20.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php73.git",
"reference": "8ff431c517be11c78c48a39a66d37431e26a6bed"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/8ff431c517be11c78c48a39a66d37431e26a6bed",
"reference": "8ff431c517be11c78c48a39a66d37431e26a6bed",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.20-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"autoload": {
"psr-4": {
"Symfony\\Polyfill\\Php73\\": ""
},
"files": [
"bootstrap.php"
],
"classmap": [
"Resources/stubs"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"polyfill",
"portable",
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-php73/tree/v1.20.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2020-10-23T14:02:19+00:00"
},
{
"name": "symfony/polyfill-php80",
"version": "v1.20.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php80.git",
"reference": "e70aa8b064c5b72d3df2abd5ab1e90464ad009de"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/e70aa8b064c5b72d3df2abd5ab1e90464ad009de",
"reference": "e70aa8b064c5b72d3df2abd5ab1e90464ad009de",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.20-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"autoload": {
"psr-4": {
"Symfony\\Polyfill\\Php80\\": ""
},
"files": [
"bootstrap.php"
],
"classmap": [
"Resources/stubs"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Ion Bazan",
"email": "ion.bazan@gmail.com"
},
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"polyfill",
"portable",
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-php80/tree/v1.20.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2020-10-23T14:02:19+00:00"
},
{
"name": "symfony/service-contracts",
"version": "v2.2.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/service-contracts.git",
"reference": "d15da7ba4957ffb8f1747218be9e1a121fd298a1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/service-contracts/zipball/d15da7ba4957ffb8f1747218be9e1a121fd298a1",
"reference": "d15da7ba4957ffb8f1747218be9e1a121fd298a1",
"shasum": ""
},
"require": {
"php": ">=7.2.5",
"psr/container": "^1.0"
},
"suggest": {
"symfony/service-implementation": ""
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.2-dev"
},
"thanks": {
"name": "symfony/contracts",
"url": "https://github.com/symfony/contracts"
}
},
"autoload": {
"psr-4": {
"Symfony\\Contracts\\Service\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Generic abstractions related to writing services",
"homepage": "https://symfony.com",
"keywords": [
"abstractions",
"contracts",
"decoupling",
"interfaces",
"interoperability",
"standards"
],
"support": {
"source": "https://github.com/symfony/service-contracts/tree/master"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2020-09-07T11:33:47+00:00"
},
{
"name": "symfony/string",
"version": "v5.2.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/string.git",
"reference": "40e975edadd4e32cd16f3753b3bad65d9ac48242"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/string/zipball/40e975edadd4e32cd16f3753b3bad65d9ac48242",
"reference": "40e975edadd4e32cd16f3753b3bad65d9ac48242",
"shasum": ""
},
"require": {
"php": ">=7.2.5",
"symfony/polyfill-ctype": "~1.8",
"symfony/polyfill-intl-grapheme": "~1.0",
"symfony/polyfill-intl-normalizer": "~1.0",
"symfony/polyfill-mbstring": "~1.0",
"symfony/polyfill-php80": "~1.15"
},
"require-dev": {
"symfony/error-handler": "^4.4|^5.0",
"symfony/http-client": "^4.4|^5.0",
"symfony/translation-contracts": "^1.1|^2",
"symfony/var-exporter": "^4.4|^5.0"
},
"type": "library",
"autoload": {
"psr-4": {
"Symfony\\Component\\String\\": ""
},
"files": [
"Resources/functions.php"
],
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony String component",
"homepage": "https://symfony.com",
"keywords": [
"grapheme",
"i18n",
"string",
"unicode",
"utf-8",
"utf8"
],
"support": {
"source": "https://github.com/symfony/string/tree/v5.2.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2020-10-24T12:08:07+00:00"
},
{
"name": "symfony/yaml",
"version": "v5.2.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/yaml.git",
"reference": "bb73619b2ae5121bbbcd9f191dfd53ded17ae598"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/yaml/zipball/bb73619b2ae5121bbbcd9f191dfd53ded17ae598",
"reference": "bb73619b2ae5121bbbcd9f191dfd53ded17ae598",
"shasum": ""
},
"require": {
"php": ">=7.2.5",
"symfony/deprecation-contracts": "^2.1",
"symfony/polyfill-ctype": "~1.8"
},
"conflict": {
"symfony/console": "<4.4"
},
"require-dev": {
"symfony/console": "^4.4|^5.0"
},
"suggest": {
"symfony/console": "For validating YAML files using the lint command"
},
"bin": [
"Resources/bin/yaml-lint"
],
"type": "library",
"autoload": {
"psr-4": {
"Symfony\\Component\\Yaml\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony Yaml Component",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/yaml/tree/v5.2.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2020-11-28T10:57:20+00:00"
}
],
"packages-dev": [
{
"name": "amphp/amp",
"version": "v2.5.1",
"source": {
"type": "git",
"url": "https://github.com/amphp/amp.git",
"reference": "ecdc3c476b3ccff02f8e5d5bcc04f7ccfd18751c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/amphp/amp/zipball/ecdc3c476b3ccff02f8e5d5bcc04f7ccfd18751c",
"reference": "ecdc3c476b3ccff02f8e5d5bcc04f7ccfd18751c",
"shasum": ""
},
"require": {
"php": ">=7"
},
"require-dev": {
"amphp/php-cs-fixer-config": "dev-master",
"amphp/phpunit-util": "^1",
"ext-json": "*",
"jetbrains/phpstorm-stubs": "^2019.3",
"phpunit/phpunit": "^6.0.9 | ^7",
"psalm/phar": "^3.11@dev",
"react/promise": "^2"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.x-dev"
}
},
"autoload": {
"psr-4": {
"Amp\\": "lib"
},
"files": [
"lib/functions.php",
"lib/Internal/functions.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Daniel Lowrey",
"email": "rdlowrey@php.net"
},
{
"name": "Aaron Piotrowski",
"email": "aaron@trowski.com"
},
{
"name": "Bob Weinand",
"email": "bobwei9@hotmail.com"
},
{
"name": "Niklas Keller",
"email": "me@kelunik.com"
}
],
"description": "A non-blocking concurrency framework for PHP applications.",
"homepage": "http://amphp.org/amp",
"keywords": [
"async",
"asynchronous",
"awaitable",
"concurrency",
"event",
"event-loop",
"future",
"non-blocking",
"promise"
],
"support": {
"irc": "irc://irc.freenode.org/amphp",
"issues": "https://github.com/amphp/amp/issues",
"source": "https://github.com/amphp/amp/tree/v2.5.1"
},
"funding": [
{
"url": "https://github.com/amphp",
"type": "github"
}
],
"time": "2020-11-03T16:23:45+00:00"
},
{
"name": "amphp/byte-stream",
"version": "v1.8.0",
"source": {
"type": "git",
"url": "https://github.com/amphp/byte-stream.git",
"reference": "f0c20cf598a958ba2aa8c6e5a71c697d652c7088"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/amphp/byte-stream/zipball/f0c20cf598a958ba2aa8c6e5a71c697d652c7088",
"reference": "f0c20cf598a958ba2aa8c6e5a71c697d652c7088",
"shasum": ""
},
"require": {
"amphp/amp": "^2",
"php": ">=7.1"
},
"require-dev": {
"amphp/php-cs-fixer-config": "dev-master",
"amphp/phpunit-util": "^1.4",
"friendsofphp/php-cs-fixer": "^2.3",
"jetbrains/phpstorm-stubs": "^2019.3",
"phpunit/phpunit": "^6 || ^7 || ^8",
"psalm/phar": "^3.11.4"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.x-dev"
}
},
"autoload": {
"psr-4": {
"Amp\\ByteStream\\": "lib"
},
"files": [
"lib/functions.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Aaron Piotrowski",
"email": "aaron@trowski.com"
},
{
"name": "Niklas Keller",
"email": "me@kelunik.com"
}
],
"description": "A stream abstraction to make working with non-blocking I/O simple.",
"homepage": "http://amphp.org/byte-stream",
"keywords": [
"amp",
"amphp",
"async",
"io",
"non-blocking",
"stream"
],
"support": {
"irc": "irc://irc.freenode.org/amphp",
"issues": "https://github.com/amphp/byte-stream/issues",
"source": "https://github.com/amphp/byte-stream/tree/master"
},
"time": "2020-06-29T18:35:05+00:00"
},
{
"name": "composer/package-versions-deprecated",
"version": "1.11.99.1",
"source": {
"type": "git",
"url": "https://github.com/composer/package-versions-deprecated.git",
"reference": "7413f0b55a051e89485c5cb9f765fe24bb02a7b6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/composer/package-versions-deprecated/zipball/7413f0b55a051e89485c5cb9f765fe24bb02a7b6",
"reference": "7413f0b55a051e89485c5cb9f765fe24bb02a7b6",
"shasum": ""
},
"require": {
"composer-plugin-api": "^1.1.0 || ^2.0",
"php": "^7 || ^8"
},
"replace": {
"ocramius/package-versions": "1.11.99"
},
"require-dev": {
"composer/composer": "^1.9.3 || ^2.0@dev",
"ext-zip": "^1.13",
"phpunit/phpunit": "^6.5 || ^7"
},
"type": "composer-plugin",
"extra": {
"class": "PackageVersions\\Installer",
"branch-alias": {
"dev-master": "1.x-dev"
}
},
"autoload": {
"psr-4": {
"PackageVersions\\": "src/PackageVersions"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Marco Pivetta",
"email": "ocramius@gmail.com"
},
{
"name": "Jordi Boggiano",
"email": "j.boggiano@seld.be"
}
],
"description": "Composer plugin that provides efficient querying for installed package versions (no runtime IO)",
"support": {
"issues": "https://github.com/composer/package-versions-deprecated/issues",
"source": "https://github.com/composer/package-versions-deprecated/tree/1.11.99.1"
},
"funding": [
{
"url": "https://packagist.com",
"type": "custom"
},
{
"url": "https://github.com/composer",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/composer/composer",
"type": "tidelift"
}
],
"time": "2020-11-11T10:22:58+00:00"
},
{
"name": "composer/semver",
"version": "3.2.4",
"source": {
"type": "git",
"url": "https://github.com/composer/semver.git",
"reference": "a02fdf930a3c1c3ed3a49b5f63859c0c20e10464"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/composer/semver/zipball/a02fdf930a3c1c3ed3a49b5f63859c0c20e10464",
"reference": "a02fdf930a3c1c3ed3a49b5f63859c0c20e10464",
"shasum": ""
},
"require": {
"php": "^5.3.2 || ^7.0 || ^8.0"
},
"require-dev": {
"phpstan/phpstan": "^0.12.54",
"symfony/phpunit-bridge": "^4.2 || ^5"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "3.x-dev"
}
},
"autoload": {
"psr-4": {
"Composer\\Semver\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nils Adermann",
"email": "naderman@naderman.de",
"homepage": "http://www.naderman.de"
},
{
"name": "Jordi Boggiano",
"email": "j.boggiano@seld.be",
"homepage": "http://seld.be"
},
{
"name": "Rob Bast",
"email": "rob.bast@gmail.com",
"homepage": "http://robbast.nl"
}
],
"description": "Semver library that offers utilities, version constraint parsing and validation.",
"keywords": [
"semantic",
"semver",
"validation",
"versioning"
],
"support": {
"irc": "irc://irc.freenode.org/composer",
"issues": "https://github.com/composer/semver/issues",
"source": "https://github.com/composer/semver/tree/3.2.4"
},
"funding": [
{
"url": "https://packagist.com",
"type": "custom"
},
{
"url": "https://github.com/composer",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/composer/composer",
"type": "tidelift"
}
],
"time": "2020-11-13T08:59:24+00:00"
},
{
"name": "composer/xdebug-handler",
"version": "1.4.5",
"source": {
"type": "git",
"url": "https://github.com/composer/xdebug-handler.git",
"reference": "f28d44c286812c714741478d968104c5e604a1d4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/composer/xdebug-handler/zipball/f28d44c286812c714741478d968104c5e604a1d4",
"reference": "f28d44c286812c714741478d968104c5e604a1d4",
"shasum": ""
},
"require": {
"php": "^5.3.2 || ^7.0 || ^8.0",
"psr/log": "^1.0"
},
"require-dev": {
"phpunit/phpunit": "^4.8.35 || ^5.7 || 6.5 - 8"
},
"type": "library",
"autoload": {
"psr-4": {
"Composer\\XdebugHandler\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "John Stevenson",
"email": "john-stevenson@blueyonder.co.uk"
}
],
"description": "Restarts a process without Xdebug.",
"keywords": [
"Xdebug",
"performance"
],
"support": {
"irc": "irc://irc.freenode.org/composer",
"issues": "https://github.com/composer/xdebug-handler/issues",
"source": "https://github.com/composer/xdebug-handler/tree/1.4.5"
},
"funding": [
{
"url": "https://packagist.com",
"type": "custom"
},
{
"url": "https://github.com/composer",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/composer/composer",
"type": "tidelift"
}
],
"time": "2020-11-13T08:04:11+00:00"
},
{
"name": "dnoegel/php-xdg-base-dir",
"version": "v0.1.1",
"source": {
"type": "git",
"url": "https://github.com/dnoegel/php-xdg-base-dir.git",
"reference": "8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/dnoegel/php-xdg-base-dir/zipball/8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd",
"reference": "8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd",
"shasum": ""
},
"require": {
"php": ">=5.3.2"
},
"require-dev": {
"phpunit/phpunit": "~7.0|~6.0|~5.0|~4.8.35"
},
"type": "library",
"autoload": {
"psr-4": {
"XdgBaseDir\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "implementation of xdg base directory specification for php",
"support": {
"issues": "https://github.com/dnoegel/php-xdg-base-dir/issues",
"source": "https://github.com/dnoegel/php-xdg-base-dir/tree/v0.1.1"
},
"time": "2019-12-04T15:06:13+00:00"
},
{
"name": "doctrine/instantiator",
"version": "1.4.0",
"source": {
"type": "git",
"url": "https://github.com/doctrine/instantiator.git",
"reference": "d56bf6102915de5702778fe20f2de3b2fe570b5b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/doctrine/instantiator/zipball/d56bf6102915de5702778fe20f2de3b2fe570b5b",
"reference": "d56bf6102915de5702778fe20f2de3b2fe570b5b",
"shasum": ""
},
"require": {
"php": "^7.1 || ^8.0"
},
"require-dev": {
"doctrine/coding-standard": "^8.0",
"ext-pdo": "*",
"ext-phar": "*",
"phpbench/phpbench": "^0.13 || 1.0.0-alpha2",
"phpstan/phpstan": "^0.12",
"phpstan/phpstan-phpunit": "^0.12",
"phpunit/phpunit": "^7.0 || ^8.0 || ^9.0"
},
"type": "library",
"autoload": {
"psr-4": {
"Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Marco Pivetta",
"email": "ocramius@gmail.com",
"homepage": "https://ocramius.github.io/"
}
],
"description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors",
"homepage": "https://www.doctrine-project.org/projects/instantiator.html",
"keywords": [
"constructor",
"instantiate"
],
"support": {
"issues": "https://github.com/doctrine/instantiator/issues",
"source": "https://github.com/doctrine/instantiator/tree/1.4.0"
},
"funding": [
{
"url": "https://www.doctrine-project.org/sponsorship.html",
"type": "custom"
},
{
"url": "https://www.patreon.com/phpdoctrine",
"type": "patreon"
},
{
"url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator",
"type": "tidelift"
}
],
"time": "2020-11-10T18:47:58+00:00"
},
{
"name": "felixfbecker/advanced-json-rpc",
"version": "v3.1.1",
"source": {
"type": "git",
"url": "https://github.com/felixfbecker/php-advanced-json-rpc.git",
"reference": "0ed363f8de17d284d479ec813c9ad3f6834b5c40"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/felixfbecker/php-advanced-json-rpc/zipball/0ed363f8de17d284d479ec813c9ad3f6834b5c40",
"reference": "0ed363f8de17d284d479ec813c9ad3f6834b5c40",
"shasum": ""
},
"require": {
"netresearch/jsonmapper": "^1.0 || ^2.0",
"php": ">=7.0",
"phpdocumentor/reflection-docblock": "^4.0.0 || ^5.0.0"
},
"require-dev": {
"phpunit/phpunit": "^6.0.0"
},
"type": "library",
"autoload": {
"psr-4": {
"AdvancedJsonRpc\\": "lib/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"ISC"
],
"authors": [
{
"name": "Felix Becker",
"email": "felix.b@outlook.com"
}
],
"description": "A more advanced JSONRPC implementation",
"support": {
"issues": "https://github.com/felixfbecker/php-advanced-json-rpc/issues",
"source": "https://github.com/felixfbecker/php-advanced-json-rpc/tree/master"
},
"time": "2020-03-11T15:21:41+00:00"
},
{
"name": "felixfbecker/language-server-protocol",
"version": "v1.5.0",
"source": {
"type": "git",
"url": "https://github.com/felixfbecker/php-language-server-protocol.git",
"reference": "85e83cacd2ed573238678c6875f8f0d7ec699541"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/felixfbecker/php-language-server-protocol/zipball/85e83cacd2ed573238678c6875f8f0d7ec699541",
"reference": "85e83cacd2ed573238678c6875f8f0d7ec699541",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"require-dev": {
"phpstan/phpstan": "*",
"squizlabs/php_codesniffer": "^3.1",
"vimeo/psalm": "^4.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.x-dev"
}
},
"autoload": {
"psr-4": {
"LanguageServerProtocol\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"ISC"
],
"authors": [
{
"name": "Felix Becker",
"email": "felix.b@outlook.com"
}
],
"description": "PHP classes for the Language Server Protocol",
"keywords": [
"language",
"microsoft",
"php",
"server"
],
"support": {
"issues": "https://github.com/felixfbecker/php-language-server-protocol/issues",
"source": "https://github.com/felixfbecker/php-language-server-protocol/tree/v1.5.0"
},
"time": "2020-10-23T13:55:30+00:00"
},
{
"name": "mikey179/vfsstream",
"version": "v1.6.8",
"source": {
"type": "git",
"url": "https://github.com/bovigo/vfsStream.git",
"reference": "231c73783ebb7dd9ec77916c10037eff5a2b6efe"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/bovigo/vfsStream/zipball/231c73783ebb7dd9ec77916c10037eff5a2b6efe",
"reference": "231c73783ebb7dd9ec77916c10037eff5a2b6efe",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"require-dev": {
"phpunit/phpunit": "^4.5|^5.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.6.x-dev"
}
},
"autoload": {
"psr-0": {
"org\\bovigo\\vfs\\": "src/main/php"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Frank Kleine",
"homepage": "http://frankkleine.de/",
"role": "Developer"
}
],
"description": "Virtual file system to mock the real file system in unit tests.",
"homepage": "http://vfs.bovigo.org/",
"support": {
"issues": "https://github.com/bovigo/vfsStream/issues",
"source": "https://github.com/bovigo/vfsStream/tree/master",
"wiki": "https://github.com/bovigo/vfsStream/wiki"
},
"time": "2019-10-30T15:31:00+00:00"
},
{
"name": "myclabs/deep-copy",
"version": "1.10.2",
"source": {
"type": "git",
"url": "https://github.com/myclabs/DeepCopy.git",
"reference": "776f831124e9c62e1a2c601ecc52e776d8bb7220"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/776f831124e9c62e1a2c601ecc52e776d8bb7220",
"reference": "776f831124e9c62e1a2c601ecc52e776d8bb7220",
"shasum": ""
},
"require": {
"php": "^7.1 || ^8.0"
},
"replace": {
"myclabs/deep-copy": "self.version"
},
"require-dev": {
"doctrine/collections": "^1.0",
"doctrine/common": "^2.6",
"phpunit/phpunit": "^7.1"
},
"type": "library",
"autoload": {
"psr-4": {
"DeepCopy\\": "src/DeepCopy/"
},
"files": [
"src/DeepCopy/deep_copy.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "Create deep copies (clones) of your objects",
"keywords": [
"clone",
"copy",
"duplicate",
"object",
"object graph"
],
"support": {
"issues": "https://github.com/myclabs/DeepCopy/issues",
"source": "https://github.com/myclabs/DeepCopy/tree/1.10.2"
},
"funding": [
{
"url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy",
"type": "tidelift"
}
],
"time": "2020-11-13T09:40:50+00:00"
},
{
"name": "netresearch/jsonmapper",
"version": "v2.1.0",
"source": {
"type": "git",
"url": "https://github.com/cweiske/jsonmapper.git",
"reference": "e0f1e33a71587aca81be5cffbb9746510e1fe04e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/cweiske/jsonmapper/zipball/e0f1e33a71587aca81be5cffbb9746510e1fe04e",
"reference": "e0f1e33a71587aca81be5cffbb9746510e1fe04e",
"shasum": ""
},
"require": {
"ext-json": "*",
"ext-pcre": "*",
"ext-reflection": "*",
"ext-spl": "*",
"php": ">=5.6"
},
"require-dev": {
"phpunit/phpunit": "~4.8.35 || ~5.7 || ~6.4 || ~7.0",
"squizlabs/php_codesniffer": "~3.5"
},
"type": "library",
"autoload": {
"psr-0": {
"JsonMapper": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"OSL-3.0"
],
"authors": [
{
"name": "Christian Weiske",
"email": "cweiske@cweiske.de",
"homepage": "http://github.com/cweiske/jsonmapper/",
"role": "Developer"
}
],
"description": "Map nested JSON structures onto PHP classes",
"support": {
"email": "cweiske@cweiske.de",
"issues": "https://github.com/cweiske/jsonmapper/issues",
"source": "https://github.com/cweiske/jsonmapper/tree/master"
},
"time": "2020-04-16T18:48:43+00:00"
},
{
"name": "nikic/php-parser",
"version": "v4.10.3",
"source": {
"type": "git",
"url": "https://github.com/nikic/PHP-Parser.git",
"reference": "dbe56d23de8fcb157bbc0cfb3ad7c7de0cfb0984"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/dbe56d23de8fcb157bbc0cfb3ad7c7de0cfb0984",
"reference": "dbe56d23de8fcb157bbc0cfb3ad7c7de0cfb0984",
"shasum": ""
},
"require": {
"ext-tokenizer": "*",
"php": ">=7.0"
},
"require-dev": {
"ircmaxell/php-yacc": "^0.0.7",
"phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0"
},
"bin": [
"bin/php-parse"
],
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "4.9-dev"
}
},
"autoload": {
"psr-4": {
"PhpParser\\": "lib/PhpParser"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Nikita Popov"
}
],
"description": "A PHP parser written in PHP",
"keywords": [
"parser",
"php"
],
"support": {
"issues": "https://github.com/nikic/PHP-Parser/issues",
"source": "https://github.com/nikic/PHP-Parser/tree/v4.10.3"
},
"time": "2020-12-03T17:45:45+00:00"
},
{
"name": "openlss/lib-array2xml",
"version": "1.0.0",
"source": {
"type": "git",
"url": "https://github.com/nullivex/lib-array2xml.git",
"reference": "a91f18a8dfc69ffabe5f9b068bc39bb202c81d90"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nullivex/lib-array2xml/zipball/a91f18a8dfc69ffabe5f9b068bc39bb202c81d90",
"reference": "a91f18a8dfc69ffabe5f9b068bc39bb202c81d90",
"shasum": ""
},
"require": {
"php": ">=5.3.2"
},
"type": "library",
"autoload": {
"psr-0": {
"LSS": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"Apache-2.0"
],
"authors": [
{
"name": "Bryan Tong",
"email": "bryan@nullivex.com",
"homepage": "https://www.nullivex.com"
},
{
"name": "Tony Butler",
"email": "spudz76@gmail.com",
"homepage": "https://www.nullivex.com"
}
],
"description": "Array2XML conversion library credit to lalit.org",
"homepage": "https://www.nullivex.com",
"keywords": [
"array",
"array conversion",
"xml",
"xml conversion"
],
"support": {
"issues": "https://github.com/nullivex/lib-array2xml/issues",
"source": "https://github.com/nullivex/lib-array2xml/tree/master"
},
"time": "2019-03-29T20:06:56+00:00"
},
{
"name": "phar-io/manifest",
"version": "2.0.1",
"source": {
"type": "git",
"url": "https://github.com/phar-io/manifest.git",
"reference": "85265efd3af7ba3ca4b2a2c34dbfc5788dd29133"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phar-io/manifest/zipball/85265efd3af7ba3ca4b2a2c34dbfc5788dd29133",
"reference": "85265efd3af7ba3ca4b2a2c34dbfc5788dd29133",
"shasum": ""
},
"require": {
"ext-dom": "*",
"ext-phar": "*",
"ext-xmlwriter": "*",
"phar-io/version": "^3.0.1",
"php": "^7.2 || ^8.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.0.x-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Arne Blankerts",
"email": "arne@blankerts.de",
"role": "Developer"
},
{
"name": "Sebastian Heuer",
"email": "sebastian@phpeople.de",
"role": "Developer"
},
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de",
"role": "Developer"
}
],
"description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)",
"support": {
"issues": "https://github.com/phar-io/manifest/issues",
"source": "https://github.com/phar-io/manifest/tree/master"
},
"time": "2020-06-27T14:33:11+00:00"
},
{
"name": "phar-io/version",
"version": "3.0.4",
"source": {
"type": "git",
"url": "https://github.com/phar-io/version.git",
"reference": "e4782611070e50613683d2b9a57730e9a3ba5451"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phar-io/version/zipball/e4782611070e50613683d2b9a57730e9a3ba5451",
"reference": "e4782611070e50613683d2b9a57730e9a3ba5451",
"shasum": ""
},
"require": {
"php": "^7.2 || ^8.0"
},
"type": "library",
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Arne Blankerts",
"email": "arne@blankerts.de",
"role": "Developer"
},
{
"name": "Sebastian Heuer",
"email": "sebastian@phpeople.de",
"role": "Developer"
},
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de",
"role": "Developer"
}
],
"description": "Library for handling version information and constraints",
"support": {
"issues": "https://github.com/phar-io/version/issues",
"source": "https://github.com/phar-io/version/tree/3.0.4"
},
"time": "2020-12-13T23:18:30+00:00"
},
{
"name": "phpdocumentor/reflection-common",
"version": "2.2.0",
"source": {
"type": "git",
"url": "https://github.com/phpDocumentor/ReflectionCommon.git",
"reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b",
"reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b",
"shasum": ""
},
"require": {
"php": "^7.2 || ^8.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-2.x": "2.x-dev"
}
},
"autoload": {
"psr-4": {
"phpDocumentor\\Reflection\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Jaap van Otterdijk",
"email": "opensource@ijaap.nl"
}
],
"description": "Common reflection classes used by phpdocumentor to reflect the code structure",
"homepage": "http://www.phpdoc.org",
"keywords": [
"FQSEN",
"phpDocumentor",
"phpdoc",
"reflection",
"static analysis"
],
"support": {
"issues": "https://github.com/phpDocumentor/ReflectionCommon/issues",
"source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x"
},
"time": "2020-06-27T09:03:43+00:00"
},
{
"name": "phpdocumentor/reflection-docblock",
"version": "5.2.2",
"source": {
"type": "git",
"url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
"reference": "069a785b2141f5bcf49f3e353548dc1cce6df556"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/069a785b2141f5bcf49f3e353548dc1cce6df556",
"reference": "069a785b2141f5bcf49f3e353548dc1cce6df556",
"shasum": ""
},
"require": {
"ext-filter": "*",
"php": "^7.2 || ^8.0",
"phpdocumentor/reflection-common": "^2.2",
"phpdocumentor/type-resolver": "^1.3",
"webmozart/assert": "^1.9.1"
},
"require-dev": {
"mockery/mockery": "~1.3.2"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "5.x-dev"
}
},
"autoload": {
"psr-4": {
"phpDocumentor\\Reflection\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Mike van Riel",
"email": "me@mikevanriel.com"
},
{
"name": "Jaap van Otterdijk",
"email": "account@ijaap.nl"
}
],
"description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.",
"support": {
"issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues",
"source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/master"
},
"time": "2020-09-03T19:13:55+00:00"
},
{
"name": "phpdocumentor/type-resolver",
"version": "1.4.0",
"source": {
"type": "git",
"url": "https://github.com/phpDocumentor/TypeResolver.git",
"reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0",
"reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0",
"shasum": ""
},
"require": {
"php": "^7.2 || ^8.0",
"phpdocumentor/reflection-common": "^2.0"
},
"require-dev": {
"ext-tokenizer": "*"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-1.x": "1.x-dev"
}
},
"autoload": {
"psr-4": {
"phpDocumentor\\Reflection\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Mike van Riel",
"email": "me@mikevanriel.com"
}
],
"description": "A PSR-5 based resolver of Class names, Types and Structural Element Names",
"support": {
"issues": "https://github.com/phpDocumentor/TypeResolver/issues",
"source": "https://github.com/phpDocumentor/TypeResolver/tree/1.4.0"
},
"time": "2020-09-17T18:55:26+00:00"
},
{
"name": "phpspec/prophecy",
"version": "1.12.1",
"source": {
"type": "git",
"url": "https://github.com/phpspec/prophecy.git",
"reference": "8ce87516be71aae9b956f81906aaf0338e0d8a2d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpspec/prophecy/zipball/8ce87516be71aae9b956f81906aaf0338e0d8a2d",
"reference": "8ce87516be71aae9b956f81906aaf0338e0d8a2d",
"shasum": ""
},
"require": {
"doctrine/instantiator": "^1.2",
"php": "^7.2 || ~8.0, <8.1",
"phpdocumentor/reflection-docblock": "^5.2",
"sebastian/comparator": "^3.0 || ^4.0",
"sebastian/recursion-context": "^3.0 || ^4.0"
},
"require-dev": {
"phpspec/phpspec": "^6.0",
"phpunit/phpunit": "^8.0 || ^9.0 <9.3"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.11.x-dev"
}
},
"autoload": {
"psr-4": {
"Prophecy\\": "src/Prophecy"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Konstantin Kudryashov",
"email": "ever.zet@gmail.com",
"homepage": "http://everzet.com"
},
{
"name": "Marcello Duarte",
"email": "marcello.duarte@gmail.com"
}
],
"description": "Highly opinionated mocking framework for PHP 5.3+",
"homepage": "https://github.com/phpspec/prophecy",
"keywords": [
"Double",
"Dummy",
"fake",
"mock",
"spy",
"stub"
],
"support": {
"issues": "https://github.com/phpspec/prophecy/issues",
"source": "https://github.com/phpspec/prophecy/tree/1.12.1"
},
"time": "2020-09-29T09:10:42+00:00"
},
{
"name": "phpunit/php-code-coverage",
"version": "7.0.14",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
"reference": "bb7c9a210c72e4709cdde67f8b7362f672f2225c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/bb7c9a210c72e4709cdde67f8b7362f672f2225c",
"reference": "bb7c9a210c72e4709cdde67f8b7362f672f2225c",
"shasum": ""
},
"require": {
"ext-dom": "*",
"ext-xmlwriter": "*",
"php": ">=7.2",
"phpunit/php-file-iterator": "^2.0.2",
"phpunit/php-text-template": "^1.2.1",
"phpunit/php-token-stream": "^3.1.1 || ^4.0",
"sebastian/code-unit-reverse-lookup": "^1.0.1",
"sebastian/environment": "^4.2.2",
"sebastian/version": "^2.0.1",
"theseer/tokenizer": "^1.1.3"
},
"require-dev": {
"phpunit/phpunit": "^8.2.2"
},
"suggest": {
"ext-xdebug": "^2.7.2"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "7.0-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de",
"role": "lead"
}
],
"description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.",
"homepage": "https://github.com/sebastianbergmann/php-code-coverage",
"keywords": [
"coverage",
"testing",
"xunit"
],
"support": {
"issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/7.0.14"
},
"funding": [
{
"url": "https://github.com/sebastianbergmann",
"type": "github"
}
],
"time": "2020-12-02T13:39:03+00:00"
},
{
"name": "phpunit/php-file-iterator",
"version": "2.0.3",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-file-iterator.git",
"reference": "4b49fb70f067272b659ef0174ff9ca40fdaa6357"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/4b49fb70f067272b659ef0174ff9ca40fdaa6357",
"reference": "4b49fb70f067272b659ef0174ff9ca40fdaa6357",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"require-dev": {
"phpunit/phpunit": "^8.5"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.0.x-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de",
"role": "lead"
}
],
"description": "FilterIterator implementation that filters files based on a list of suffixes.",
"homepage": "https://github.com/sebastianbergmann/php-file-iterator/",
"keywords": [
"filesystem",
"iterator"
],
"support": {
"issues": "https://github.com/sebastianbergmann/php-file-iterator/issues",
"source": "https://github.com/sebastianbergmann/php-file-iterator/tree/2.0.3"
},
"funding": [
{
"url": "https://github.com/sebastianbergmann",
"type": "github"
}
],
"time": "2020-11-30T08:25:21+00:00"
},
{
"name": "phpunit/php-text-template",
"version": "1.2.1",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-text-template.git",
"reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
"reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"type": "library",
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de",
"role": "lead"
}
],
"description": "Simple template engine.",
"homepage": "https://github.com/sebastianbergmann/php-text-template/",
"keywords": [
"template"
],
"support": {
"issues": "https://github.com/sebastianbergmann/php-text-template/issues",
"source": "https://github.com/sebastianbergmann/php-text-template/tree/1.2.1"
},
"time": "2015-06-21T13:50:34+00:00"
},
{
"name": "phpunit/php-timer",
"version": "2.1.3",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-timer.git",
"reference": "2454ae1765516d20c4ffe103d85a58a9a3bd5662"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/2454ae1765516d20c4ffe103d85a58a9a3bd5662",
"reference": "2454ae1765516d20c4ffe103d85a58a9a3bd5662",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"require-dev": {
"phpunit/phpunit": "^8.5"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.1-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de",
"role": "lead"
}
],
"description": "Utility class for timing",
"homepage": "https://github.com/sebastianbergmann/php-timer/",
"keywords": [
"timer"
],
"support": {
"issues": "https://github.com/sebastianbergmann/php-timer/issues",
"source": "https://github.com/sebastianbergmann/php-timer/tree/2.1.3"
},
"funding": [
{
"url": "https://github.com/sebastianbergmann",
"type": "github"
}
],
"time": "2020-11-30T08:20:02+00:00"
},
{
"name": "phpunit/php-token-stream",
"version": "4.0.4",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-token-stream.git",
"reference": "a853a0e183b9db7eed023d7933a858fa1c8d25a3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/a853a0e183b9db7eed023d7933a858fa1c8d25a3",
"reference": "a853a0e183b9db7eed023d7933a858fa1c8d25a3",
"shasum": ""
},
"require": {
"ext-tokenizer": "*",
"php": "^7.3 || ^8.0"
},
"require-dev": {
"phpunit/phpunit": "^9.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "4.0-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de"
}
],
"description": "Wrapper around PHP's tokenizer extension.",
"homepage": "https://github.com/sebastianbergmann/php-token-stream/",
"keywords": [
"tokenizer"
],
"support": {
"issues": "https://github.com/sebastianbergmann/php-token-stream/issues",
"source": "https://github.com/sebastianbergmann/php-token-stream/tree/master"
},
"funding": [
{
"url": "https://github.com/sebastianbergmann",
"type": "github"
}
],
"abandoned": true,
"time": "2020-08-04T08:28:15+00:00"
},
{
"name": "phpunit/phpunit",
"version": "8.5.13",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "8e86be391a58104ef86037ba8a846524528d784e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/8e86be391a58104ef86037ba8a846524528d784e",
"reference": "8e86be391a58104ef86037ba8a846524528d784e",
"shasum": ""
},
"require": {
"doctrine/instantiator": "^1.3.1",
"ext-dom": "*",
"ext-json": "*",
"ext-libxml": "*",
"ext-mbstring": "*",
"ext-xml": "*",
"ext-xmlwriter": "*",
"myclabs/deep-copy": "^1.10.0",
"phar-io/manifest": "^2.0.1",
"phar-io/version": "^3.0.2",
"php": ">=7.2",
"phpspec/prophecy": "^1.10.3",
"phpunit/php-code-coverage": "^7.0.12",
"phpunit/php-file-iterator": "^2.0.2",
"phpunit/php-text-template": "^1.2.1",
"phpunit/php-timer": "^2.1.2",
"sebastian/comparator": "^3.0.2",
"sebastian/diff": "^3.0.2",
"sebastian/environment": "^4.2.3",
"sebastian/exporter": "^3.1.2",
"sebastian/global-state": "^3.0.0",
"sebastian/object-enumerator": "^3.0.3",
"sebastian/resource-operations": "^2.0.1",
"sebastian/type": "^1.1.3",
"sebastian/version": "^2.0.1"
},
"require-dev": {
"ext-pdo": "*"
},
"suggest": {
"ext-soap": "*",
"ext-xdebug": "*",
"phpunit/php-invoker": "^2.0.0"
},
"bin": [
"phpunit"
],
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "8.5-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de",
"role": "lead"
}
],
"description": "The PHP Unit Testing framework.",
"homepage": "https://phpunit.de/",
"keywords": [
"phpunit",
"testing",
"xunit"
],
"support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
"source": "https://github.com/sebastianbergmann/phpunit/tree/8.5.13"
},
"funding": [
{
"url": "https://phpunit.de/donate.html",
"type": "custom"
},
{
"url": "https://github.com/sebastianbergmann",
"type": "github"
}
],
"time": "2020-12-01T04:53:52+00:00"
},
{
"name": "psr/log",
"version": "1.1.3",
"source": {
"type": "git",
"url": "https://github.com/php-fig/log.git",
"reference": "0f73288fd15629204f9d42b7055f72dacbe811fc"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/log/zipball/0f73288fd15629204f9d42b7055f72dacbe811fc",
"reference": "0f73288fd15629204f9d42b7055f72dacbe811fc",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.1.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\Log\\": "Psr/Log/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "http://www.php-fig.org/"
}
],
"description": "Common interface for logging libraries",
"homepage": "https://github.com/php-fig/log",
"keywords": [
"log",
"psr",
"psr-3"
],
"support": {
"source": "https://github.com/php-fig/log/tree/1.1.3"
},
"time": "2020-03-23T09:12:05+00:00"
},
{
"name": "sebastian/code-unit-reverse-lookup",
"version": "1.0.2",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git",
"reference": "1de8cd5c010cb153fcd68b8d0f64606f523f7619"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/1de8cd5c010cb153fcd68b8d0f64606f523f7619",
"reference": "1de8cd5c010cb153fcd68b8d0f64606f523f7619",
"shasum": ""
},
"require": {
"php": ">=5.6"
},
"require-dev": {
"phpunit/phpunit": "^8.5"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de"
}
],
"description": "Looks up which function or method a line of code belongs to",
"homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/",
"support": {
"issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues",
"source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/1.0.2"
},
"funding": [
{
"url": "https://github.com/sebastianbergmann",
"type": "github"
}
],
"time": "2020-11-30T08:15:22+00:00"
},
{
"name": "sebastian/comparator",
"version": "3.0.3",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/comparator.git",
"reference": "1071dfcef776a57013124ff35e1fc41ccd294758"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/1071dfcef776a57013124ff35e1fc41ccd294758",
"reference": "1071dfcef776a57013124ff35e1fc41ccd294758",
"shasum": ""
},
"require": {
"php": ">=7.1",
"sebastian/diff": "^3.0",
"sebastian/exporter": "^3.1"
},
"require-dev": {
"phpunit/phpunit": "^8.5"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.0-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de"
},
{
"name": "Jeff Welch",
"email": "whatthejeff@gmail.com"
},
{
"name": "Volker Dusch",
"email": "github@wallbash.com"
},
{
"name": "Bernhard Schussek",
"email": "bschussek@2bepublished.at"
}
],
"description": "Provides the functionality to compare PHP values for equality",
"homepage": "https://github.com/sebastianbergmann/comparator",
"keywords": [
"comparator",
"compare",
"equality"
],
"support": {
"issues": "https://github.com/sebastianbergmann/comparator/issues",
"source": "https://github.com/sebastianbergmann/comparator/tree/3.0.3"
},
"funding": [
{
"url": "https://github.com/sebastianbergmann",
"type": "github"
}
],
"time": "2020-11-30T08:04:30+00:00"
},
{
"name": "sebastian/diff",
"version": "3.0.3",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/diff.git",
"reference": "14f72dd46eaf2f2293cbe79c93cc0bc43161a211"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/14f72dd46eaf2f2293cbe79c93cc0bc43161a211",
"reference": "14f72dd46eaf2f2293cbe79c93cc0bc43161a211",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"require-dev": {
"phpunit/phpunit": "^7.5 || ^8.0",
"symfony/process": "^2 || ^3.3 || ^4"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.0-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de"
},
{
"name": "Kore Nordmann",
"email": "mail@kore-nordmann.de"
}
],
"description": "Diff implementation",
"homepage": "https://github.com/sebastianbergmann/diff",
"keywords": [
"diff",
"udiff",
"unidiff",
"unified diff"
],
"support": {
"issues": "https://github.com/sebastianbergmann/diff/issues",
"source": "https://github.com/sebastianbergmann/diff/tree/3.0.3"
},
"funding": [
{
"url": "https://github.com/sebastianbergmann",
"type": "github"
}
],
"time": "2020-11-30T07:59:04+00:00"
},
{
"name": "sebastian/environment",
"version": "4.2.4",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/environment.git",
"reference": "d47bbbad83711771f167c72d4e3f25f7fcc1f8b0"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/d47bbbad83711771f167c72d4e3f25f7fcc1f8b0",
"reference": "d47bbbad83711771f167c72d4e3f25f7fcc1f8b0",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"require-dev": {
"phpunit/phpunit": "^7.5"
},
"suggest": {
"ext-posix": "*"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "4.2-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de"
}
],
"description": "Provides functionality to handle HHVM/PHP environments",
"homepage": "http://www.github.com/sebastianbergmann/environment",
"keywords": [
"Xdebug",
"environment",
"hhvm"
],
"support": {
"issues": "https://github.com/sebastianbergmann/environment/issues",
"source": "https://github.com/sebastianbergmann/environment/tree/4.2.4"
},
"funding": [
{
"url": "https://github.com/sebastianbergmann",
"type": "github"
}
],
"time": "2020-11-30T07:53:42+00:00"
},
{
"name": "sebastian/exporter",
"version": "3.1.3",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/exporter.git",
"reference": "6b853149eab67d4da22291d36f5b0631c0fd856e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/6b853149eab67d4da22291d36f5b0631c0fd856e",
"reference": "6b853149eab67d4da22291d36f5b0631c0fd856e",
"shasum": ""
},
"require": {
"php": ">=7.0",
"sebastian/recursion-context": "^3.0"
},
"require-dev": {
"ext-mbstring": "*",
"phpunit/phpunit": "^6.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.1.x-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de"
},
{
"name": "Jeff Welch",
"email": "whatthejeff@gmail.com"
},
{
"name": "Volker Dusch",
"email": "github@wallbash.com"
},
{
"name": "Adam Harvey",
"email": "aharvey@php.net"
},
{
"name": "Bernhard Schussek",
"email": "bschussek@gmail.com"
}
],
"description": "Provides the functionality to export PHP variables for visualization",
"homepage": "http://www.github.com/sebastianbergmann/exporter",
"keywords": [
"export",
"exporter"
],
"support": {
"issues": "https://github.com/sebastianbergmann/exporter/issues",
"source": "https://github.com/sebastianbergmann/exporter/tree/3.1.3"
},
"funding": [
{
"url": "https://github.com/sebastianbergmann",
"type": "github"
}
],
"time": "2020-11-30T07:47:53+00:00"
},
{
"name": "sebastian/global-state",
"version": "3.0.1",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/global-state.git",
"reference": "474fb9edb7ab891665d3bfc6317f42a0a150454b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/474fb9edb7ab891665d3bfc6317f42a0a150454b",
"reference": "474fb9edb7ab891665d3bfc6317f42a0a150454b",
"shasum": ""
},
"require": {
"php": ">=7.2",
"sebastian/object-reflector": "^1.1.1",
"sebastian/recursion-context": "^3.0"
},
"require-dev": {
"ext-dom": "*",
"phpunit/phpunit": "^8.0"
},
"suggest": {
"ext-uopz": "*"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.0-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de"
}
],
"description": "Snapshotting of global state",
"homepage": "http://www.github.com/sebastianbergmann/global-state",
"keywords": [
"global state"
],
"support": {
"issues": "https://github.com/sebastianbergmann/global-state/issues",
"source": "https://github.com/sebastianbergmann/global-state/tree/3.0.1"
},
"funding": [
{
"url": "https://github.com/sebastianbergmann",
"type": "github"
}
],
"time": "2020-11-30T07:43:24+00:00"
},
{
"name": "sebastian/object-enumerator",
"version": "3.0.4",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/object-enumerator.git",
"reference": "e67f6d32ebd0c749cf9d1dbd9f226c727043cdf2"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/e67f6d32ebd0c749cf9d1dbd9f226c727043cdf2",
"reference": "e67f6d32ebd0c749cf9d1dbd9f226c727043cdf2",
"shasum": ""
},
"require": {
"php": ">=7.0",
"sebastian/object-reflector": "^1.1.1",
"sebastian/recursion-context": "^3.0"
},
"require-dev": {
"phpunit/phpunit": "^6.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.0.x-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de"
}
],
"description": "Traverses array structures and object graphs to enumerate all referenced objects",
"homepage": "https://github.com/sebastianbergmann/object-enumerator/",
"support": {
"issues": "https://github.com/sebastianbergmann/object-enumerator/issues",
"source": "https://github.com/sebastianbergmann/object-enumerator/tree/3.0.4"
},
"funding": [
{
"url": "https://github.com/sebastianbergmann",
"type": "github"
}
],
"time": "2020-11-30T07:40:27+00:00"
},
{
"name": "sebastian/object-reflector",
"version": "1.1.2",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/object-reflector.git",
"reference": "9b8772b9cbd456ab45d4a598d2dd1a1bced6363d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/9b8772b9cbd456ab45d4a598d2dd1a1bced6363d",
"reference": "9b8772b9cbd456ab45d4a598d2dd1a1bced6363d",
"shasum": ""
},
"require": {
"php": ">=7.0"
},
"require-dev": {
"phpunit/phpunit": "^6.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.1-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de"
}
],
"description": "Allows reflection of object attributes, including inherited and non-public ones",
"homepage": "https://github.com/sebastianbergmann/object-reflector/",
"support": {
"issues": "https://github.com/sebastianbergmann/object-reflector/issues",
"source": "https://github.com/sebastianbergmann/object-reflector/tree/1.1.2"
},
"funding": [
{
"url": "https://github.com/sebastianbergmann",
"type": "github"
}
],
"time": "2020-11-30T07:37:18+00:00"
},
{
"name": "sebastian/recursion-context",
"version": "3.0.1",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/recursion-context.git",
"reference": "367dcba38d6e1977be014dc4b22f47a484dac7fb"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/367dcba38d6e1977be014dc4b22f47a484dac7fb",
"reference": "367dcba38d6e1977be014dc4b22f47a484dac7fb",
"shasum": ""
},
"require": {
"php": ">=7.0"
},
"require-dev": {
"phpunit/phpunit": "^6.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.0.x-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de"
},
{
"name": "Jeff Welch",
"email": "whatthejeff@gmail.com"
},
{
"name": "Adam Harvey",
"email": "aharvey@php.net"
}
],
"description": "Provides functionality to recursively process PHP variables",
"homepage": "http://www.github.com/sebastianbergmann/recursion-context",
"support": {
"issues": "https://github.com/sebastianbergmann/recursion-context/issues",
"source": "https://github.com/sebastianbergmann/recursion-context/tree/3.0.1"
},
"funding": [
{
"url": "https://github.com/sebastianbergmann",
"type": "github"
}
],
"time": "2020-11-30T07:34:24+00:00"
},
{
"name": "sebastian/resource-operations",
"version": "2.0.2",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/resource-operations.git",
"reference": "31d35ca87926450c44eae7e2611d45a7a65ea8b3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/31d35ca87926450c44eae7e2611d45a7a65ea8b3",
"reference": "31d35ca87926450c44eae7e2611d45a7a65ea8b3",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.0-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de"
}
],
"description": "Provides a list of PHP built-in functions that operate on resources",
"homepage": "https://www.github.com/sebastianbergmann/resource-operations",
"support": {
"issues": "https://github.com/sebastianbergmann/resource-operations/issues",
"source": "https://github.com/sebastianbergmann/resource-operations/tree/2.0.2"
},
"funding": [
{
"url": "https://github.com/sebastianbergmann",
"type": "github"
}
],
"time": "2020-11-30T07:30:19+00:00"
},
{
"name": "sebastian/type",
"version": "1.1.4",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/type.git",
"reference": "0150cfbc4495ed2df3872fb31b26781e4e077eb4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/type/zipball/0150cfbc4495ed2df3872fb31b26781e4e077eb4",
"reference": "0150cfbc4495ed2df3872fb31b26781e4e077eb4",
"shasum": ""
},
"require": {
"php": ">=7.2"
},
"require-dev": {
"phpunit/phpunit": "^8.2"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.1-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de",
"role": "lead"
}
],
"description": "Collection of value objects that represent the types of the PHP type system",
"homepage": "https://github.com/sebastianbergmann/type",
"support": {
"issues": "https://github.com/sebastianbergmann/type/issues",
"source": "https://github.com/sebastianbergmann/type/tree/1.1.4"
},
"funding": [
{
"url": "https://github.com/sebastianbergmann",
"type": "github"
}
],
"time": "2020-11-30T07:25:11+00:00"
},
{
"name": "sebastian/version",
"version": "2.0.1",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/version.git",
"reference": "99732be0ddb3361e16ad77b68ba41efc8e979019"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019",
"reference": "99732be0ddb3361e16ad77b68ba41efc8e979019",
"shasum": ""
},
"require": {
"php": ">=5.6"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.0.x-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de",
"role": "lead"
}
],
"description": "Library that helps with managing the version number of Git-hosted PHP projects",
"homepage": "https://github.com/sebastianbergmann/version",
"support": {
"issues": "https://github.com/sebastianbergmann/version/issues",
"source": "https://github.com/sebastianbergmann/version/tree/master"
},
"time": "2016-10-03T07:35:21+00:00"
},
{
"name": "theseer/tokenizer",
"version": "1.2.0",
"source": {
"type": "git",
"url": "https://github.com/theseer/tokenizer.git",
"reference": "75a63c33a8577608444246075ea0af0d052e452a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/theseer/tokenizer/zipball/75a63c33a8577608444246075ea0af0d052e452a",
"reference": "75a63c33a8577608444246075ea0af0d052e452a",
"shasum": ""
},
"require": {
"ext-dom": "*",
"ext-tokenizer": "*",
"ext-xmlwriter": "*",
"php": "^7.2 || ^8.0"
},
"type": "library",
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Arne Blankerts",
"email": "arne@blankerts.de",
"role": "Developer"
}
],
"description": "A small library for converting tokenized PHP source code into XML and potentially other formats",
"support": {
"issues": "https://github.com/theseer/tokenizer/issues",
"source": "https://github.com/theseer/tokenizer/tree/master"
},
"funding": [
{
"url": "https://github.com/theseer",
"type": "github"
}
],
"time": "2020-07-12T23:59:07+00:00"
},
{
"name": "vimeo/psalm",
"version": "4.3.1",
"source": {
"type": "git",
"url": "https://github.com/vimeo/psalm.git",
"reference": "2feba22a005a18bf31d4c7b9bdb9252c73897476"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/vimeo/psalm/zipball/2feba22a005a18bf31d4c7b9bdb9252c73897476",
"reference": "2feba22a005a18bf31d4c7b9bdb9252c73897476",
"shasum": ""
},
"require": {
"amphp/amp": "^2.1",
"amphp/byte-stream": "^1.5",
"composer/package-versions-deprecated": "^1.8.0",
"composer/semver": "^1.4 || ^2.0 || ^3.0",
"composer/xdebug-handler": "^1.1",
"dnoegel/php-xdg-base-dir": "^0.1.1",
"ext-dom": "*",
"ext-json": "*",
"ext-libxml": "*",
"ext-mbstring": "*",
"ext-simplexml": "*",
"ext-tokenizer": "*",
"felixfbecker/advanced-json-rpc": "^3.0.3",
"felixfbecker/language-server-protocol": "^1.4",
"netresearch/jsonmapper": "^1.0 || ^2.0 || ^3.0",
"nikic/php-parser": "^4.10.1",
"openlss/lib-array2xml": "^1.0",
"php": "^7.1|^8",
"sebastian/diff": "^3.0 || ^4.0",
"symfony/console": "^3.4.17 || ^4.1.6 || ^5.0",
"webmozart/path-util": "^2.3"
},
"provide": {
"psalm/psalm": "self.version"
},
"require-dev": {
"amphp/amp": "^2.4.2",
"bamarni/composer-bin-plugin": "^1.2",
"brianium/paratest": "^4.0.0",
"ext-curl": "*",
"php": "^7.3|^8",
"phpdocumentor/reflection-docblock": "^5",
"phpmyadmin/sql-parser": "5.1.0",
"phpspec/prophecy": ">=1.9.0",
"phpunit/phpunit": "^9.0",
"psalm/plugin-phpunit": "^0.13",
"slevomat/coding-standard": "^5.0",
"squizlabs/php_codesniffer": "^3.5",
"symfony/process": "^4.3",
"weirdan/prophecy-shim": "^1.0 || ^2.0"
},
"suggest": {
"ext-igbinary": "^2.0.5"
},
"bin": [
"psalm",
"psalm-language-server",
"psalm-plugin",
"psalm-refactor",
"psalter"
],
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "4.x-dev",
"dev-3.x": "3.x-dev",
"dev-2.x": "2.x-dev",
"dev-1.x": "1.x-dev"
}
},
"autoload": {
"psr-4": {
"Psalm\\": "src/Psalm/"
},
"files": [
"src/functions.php",
"src/spl_object_id.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Matthew Brown"
}
],
"description": "A static analysis tool for finding errors in PHP applications",
"keywords": [
"code",
"inspection",
"php"
],
"support": {
"issues": "https://github.com/vimeo/psalm/issues",
"source": "https://github.com/vimeo/psalm/tree/4.3.1"
},
"time": "2020-12-03T16:44:10+00:00"
},
{
"name": "webmozart/assert",
"version": "1.9.1",
"source": {
"type": "git",
"url": "https://github.com/webmozart/assert.git",
"reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/webmozart/assert/zipball/bafc69caeb4d49c39fd0779086c03a3738cbb389",
"reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389",
"shasum": ""
},
"require": {
"php": "^5.3.3 || ^7.0 || ^8.0",
"symfony/polyfill-ctype": "^1.8"
},
"conflict": {
"phpstan/phpstan": "<0.12.20",
"vimeo/psalm": "<3.9.1"
},
"require-dev": {
"phpunit/phpunit": "^4.8.36 || ^7.5.13"
},
"type": "library",
"autoload": {
"psr-4": {
"Webmozart\\Assert\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Bernhard Schussek",
"email": "bschussek@gmail.com"
}
],
"description": "Assertions to validate method input/output with nice error messages.",
"keywords": [
"assert",
"check",
"validate"
],
"support": {
"issues": "https://github.com/webmozart/assert/issues",
"source": "https://github.com/webmozart/assert/tree/master"
},
"time": "2020-07-08T17:02:28+00:00"
},
{
"name": "webmozart/path-util",
"version": "2.3.0",
"source": {
"type": "git",
"url": "https://github.com/webmozart/path-util.git",
"reference": "d939f7edc24c9a1bb9c0dee5cb05d8e859490725"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/webmozart/path-util/zipball/d939f7edc24c9a1bb9c0dee5cb05d8e859490725",
"reference": "d939f7edc24c9a1bb9c0dee5cb05d8e859490725",
"shasum": ""
},
"require": {
"php": ">=5.3.3",
"webmozart/assert": "~1.0"
},
"require-dev": {
"phpunit/phpunit": "^4.6",
"sebastian/version": "^1.0.1"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.3-dev"
}
},
"autoload": {
"psr-4": {
"Webmozart\\PathUtil\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Bernhard Schussek",
"email": "bschussek@gmail.com"
}
],
"description": "A robust cross-platform utility for normalizing, comparing and modifying file paths.",
"support": {
"issues": "https://github.com/webmozart/path-util/issues",
"source": "https://github.com/webmozart/path-util/tree/2.3.0"
},
"time": "2015-12-17T08:42:14+00:00"
}
],
"aliases": [],
"minimum-stability": "dev",
"stability-flags": [],
"prefer-stable": true,
"prefer-lowest": false,
"platform": {
"php": "^7.2",
"ext-json": "*"
},
"platform-dev": [],
"plugin-api-version": "2.0.0"
}
{
"name": "helmich/typo3-typoscript-lint",
"description": "Static code analysis for the TypoScript configuration language.",
"type": "library",
"license": "MIT",
"homepage": "https://github.com/martin-helmich",
"authors": [
{
"name": "Martin Helmich",
"email": "m.helmich@mittwald.de",
"role": "lead"
}
],
"support": {
"issues": "https://github.com/martin-helmich/typo3-typoscript-lint/issues"
},
"minimum-stability": "dev",
"prefer-stable": true,
"require": {
"php": "^7.2",
"symfony/console": "~3.0|~4.0|~5.0",
"symfony/dependency-injection": "~3.0|~4.0|~5.0",
"symfony/config": "~3.0|~4.0|~5.0",
"symfony/yaml": "~3.0|~4.0|~5.0",
"symfony/finder": "~3.0|~4.0|~5.0",
"symfony/filesystem": "~3.0|~4.0|~5.0",
"symfony/event-dispatcher": "~3.0|~4.0|~5.0",
"helmich/typo3-typoscript-parser": "^2.1",
"ext-json": "*"
},
"require-dev": {
"phpunit/phpunit": "^8.0",
"mikey179/vfsstream": "^1.6",
"vimeo/psalm": "^3.7 || ^4.0"
},
"scripts": {
"package": "bash .build/package.sh $@"
},
"bin": [
"typoscript-lint"
],
"autoload": {
"psr-4": {
"Helmich\\TypoScriptLint\\": "src/"
}
},
"autoload-dev": {
"files": [
"vendor/phpunit/phpunit/src/Framework/Assert/Functions.php"
],
"psr-4": {
"Helmich\\TypoScriptLint\\Tests\\Functional\\": "tests/functional",
"Helmich\\TypoScriptLint\\Tests\\Unit\\": "tests/unit"
}
}
}<?php declare(strict_types=1);
namespace Helmich\TypoScriptLint\Util;
class CallbackFinderObserver implements FinderObserver
{
/** @var callable */
private $fn;
public function __construct(callable $fn)
{
$this->fn = $fn;
}
public function onEntryNotFound(string $fileOrDirectory): void
{
// Because, fuck you, PHP
$fn = $this->fn;
$fn($fileOrDirectory);
}
}<?php declare(strict_types=1);
namespace Helmich\TypoScriptLint\Util;
use SplFileInfo;
use Symfony\Component\Finder\Finder as SymfonyFinder;
use Symfony\Component\Finder\SplFileInfo as SymfonySplFileInfo;
/**
* Helper class that selects files to analyze from a list of file and directory names.
*
* @author Martin Helmich <typo3@martin-helmich.de>
* @license MIT
* @package Helmich\TypoScriptLint
* @subpackage Util
*/
class Finder
{
/** @var SymfonyFinder */
private $finder;
/** @var Filesystem */
private $filesystem;
/**
* Constructs a new finder instance.
*
* @param SymfonyFinder $finder A finder.
* @param Filesystem $filesystem A filesystem interface.
*/
public function __construct(SymfonyFinder $finder, Filesystem $filesystem)
{
$this->finder = $finder;
$this->filesystem = $filesystem;
}
/**
* Generates a list of file names from a list of file and directory names.
*
* @param string[] $fileOrDirectoryNames A list of file and directory names.
* @param string[] $filePatterns Glob patterns that filenames should match
* @param string[] $excludePatterns Glob patterns of files that should be excluded, even if matched
* @param FinderObserver|null $observer
* @return string[] A list of file names.
*/
public function getFilenames(array $fileOrDirectoryNames, array $filePatterns = [], array $excludePatterns = [], FinderObserver $observer = null): array
{
$finder = clone $this->finder;
$finder->files();
$observer = $observer ?: new CallbackFinderObserver(function() {});
$matchesPatternList = function(array $patterns): callable {
return function(string $file) use ($patterns): bool {
foreach($patterns as $pattern) {
if (fnmatch($pattern, $file)) {
return true;
}
}
return false;
};
};
$matchesFilePattern = $matchesPatternList($filePatterns);
$matchesExcludePattern = $matchesPatternList($excludePatterns);
if (count($filePatterns) > 0) {
$finder->filter(function (SplFileInfo $fileInfo) use ($matchesFilePattern, $matchesExcludePattern) {
if ($fileInfo->isDir()) {
return true;
}
$fileName = $fileInfo->getFilename();
return $matchesFilePattern($fileName) && !$matchesExcludePattern($fileName);
});
}
$filenames = [];
$globbedFileOrDirectoryNames = [];
foreach($fileOrDirectoryNames as $fileOrDirectoryName) {
if (strpos($fileOrDirectoryName, "*") !== false) {
$files = glob($fileOrDirectoryName);
if ($files === false) {
continue;
}
$globbedFileOrDirectoryNames = array_merge($globbedFileOrDirectoryNames, $files);
} else {
$globbedFileOrDirectoryNames[] = $fileOrDirectoryName;
}
}
foreach ($globbedFileOrDirectoryNames as $fileOrDirectoryName) {
$subFinder = clone $finder;
$resolvedFileOrDirectoryName = $fileOrDirectoryName;
if ($fileOrDirectoryName[0] !== '/' && substr($fileOrDirectoryName, 0, 6) !== 'vfs://') {
$resolvedFileOrDirectoryName = realpath($fileOrDirectoryName);
if ($resolvedFileOrDirectoryName === false) {
$observer->onEntryNotFound($fileOrDirectoryName);
continue;
}
}
if (file_exists($resolvedFileOrDirectoryName) === false) {
$observer->onEntryNotFound($resolvedFileOrDirectoryName);
continue;
}
$fileInfo = $this->filesystem->openFile($resolvedFileOrDirectoryName);
if ($fileInfo->isFile()) {
$filenames[] = $resolvedFileOrDirectoryName;
} else {
$subFinder->in($resolvedFileOrDirectoryName);
/** @var SymfonySplFileInfo $subFileInfo */
foreach ($subFinder as $subFileInfo) {
$filenames[] = $subFileInfo->getPathname();
}
}
}
return $filenames;
}
}
<?php declare(strict_types=1);
namespace Helmich\TypoScriptLint\Util;
interface FinderObserver
{
public function onEntryNotFound(string $fileOrDirectory): void;
}<?php declare(strict_types=1);
namespace Helmich\TypoScriptLint\Util;
use Symfony\Component\Filesystem\Filesystem as SymfonyFilesystem;
use Symfony\Component\Finder\SplFileInfo;
class Filesystem extends SymfonyFilesystem
{
/**
* @param string $filename
* @return SplFileInfo
*/
public function openFile(string $filename): SplFileInfo
{
$start = getcwd() ?: "/";
$relative = $this->makePathRelative($filename, $start);
return new SplFileInfo($filename, dirname($relative), $relative);
}
}
<?php declare(strict_types=1);
namespace Helmich\TypoScriptLint\Command;
use Helmich\TypoScriptLint\Exception\BadOutputFileException;
use Helmich\TypoScriptLint\Linter\Configuration\ConfigurationLocator;
use Helmich\TypoScriptLint\Linter\LinterInterface;
use Helmich\TypoScriptLint\Linter\Report\Issue;
use Helmich\TypoScriptLint\Linter\Report\Report;
use Helmich\TypoScriptLint\Logging\LinterLoggerBuilder;
use Helmich\TypoScriptLint\Util\CallbackFinderObserver;
use Helmich\TypoScriptLint\Util\Finder;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\ConsoleEvents;
use Symfony\Component\Console\Event\ConsoleTerminateEvent;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Output\StreamOutput;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
/**
* Command class that performs linting on a set of TypoScript files.
*
* @author Martin Helmich <typo3@martin-helmich.de>
* @license MIT
* @package Helmich\TypoScriptLint
* @subpackage Command
*/
class LintCommand extends Command
{
/** @var LinterInterface */
private $linter;
/** @var ConfigurationLocator */
private $linterConfigurationLocator;
/** @var LinterLoggerBuilder */
private $loggerBuilder;
/** @var Finder */
private $finder;
/** @var EventDispatcherInterface */
private $eventDispatcher;
public function __construct(
LinterInterface $linter,
ConfigurationLocator $configurationLocator,
LinterLoggerBuilder $loggerBuilder,
Finder $finder,
EventDispatcherInterface $eventDispatcher
)
{
parent::__construct();
$this->linter = $linter;
$this->linterConfigurationLocator = $configurationLocator;
$this->loggerBuilder = $loggerBuilder;
$this->finder = $finder;
$this->eventDispatcher = $eventDispatcher;
}
/**
* Configures this command.
*
* @return void
*/
protected function configure(): void
{
$this
->setName('lint')
->setDescription('Check coding style for TypoScript file.')
->addOption('config', 'c', InputOption::VALUE_REQUIRED, 'Configuration file to use', 'typoscript-lint.yml')
->addOption('format', 'f', InputOption::VALUE_REQUIRED, 'Output format', 'compact')
->addOption('output', 'o', InputOption::VALUE_REQUIRED, 'Output file ("-" for stdout)', '-')
->addOption('exit-code', 'e', InputOption::VALUE_NONE, '(DEPRECATED) Set this flag to exit with a non-zero exit code when there are warnings')
->addOption('fail-on-warnings', null, InputOption::VALUE_NONE, 'Set this flag to exit with a non-zero exit code when there are warnings')
->addArgument('paths', InputArgument::OPTIONAL | InputArgument::IS_ARRAY, 'File or directory names. If omitted, the "paths" option from the configuration file will be used, if present');
}
/**
* @param string $fileName
* @return string[]
*/
private function getPossibleConfigFiles(string $fileName): array
{
if ($fileName === 'typoscript-lint.yml') {
return ["tslint.yml", "typoscript-lint.yml"];
}
return [$fileName];
}
/**
* Executes this command.
*
* @param InputInterface $input Input options.
* @param OutputInterface $output Output stream.
* @return int
*
* @throws BadOutputFileException
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
/** @var string $configFilesOption */
$configFilesOption = $input->getOption('config');
/** @var string $outputOption */
$outputOption = $input->getOption('output');
/** @var string $formatOption */
$formatOption = $input->getOption('format');
$configFiles = $this->getPossibleConfigFiles($configFilesOption);
$configuration = $this->linterConfigurationLocator->loadConfiguration($configFiles);
/** @var string[] $paths */
$paths = $input->getArgument('paths') ?: $configuration->getPaths();
$outputTarget = $input->getOption('output');
$exitWithExitCode = $input->getOption('exit-code') || $input->getOption('fail-on-warnings');
if (false == $outputTarget) {
throw new BadOutputFileException('Bad output file.');
}
if ($outputOption === '-') {
$reportOutput = $output;
} else {
$fileHandle = fopen($outputOption, 'w');
if ($fileHandle === false) {
throw new \Exception("could not open file '${outputOption}' for writing");
}
$reportOutput = new StreamOutput($fileHandle);
}
$logger = $this->loggerBuilder->createLogger($formatOption, $reportOutput, $output);
$report = new Report();
$filePatterns = $configuration->getFilePatterns();
$excludePatterns = $configuration->getExcludePatterns();
$observer = new CallbackFinderObserver(function (string $name) use ($logger): void {
$logger->notifyFileNotFound($name);
});
$files = $this->finder->getFilenames($paths, $filePatterns, $excludePatterns, $observer);
$logger->notifyFiles($files);
foreach ($files as $filename) {
$logger->notifyFileStart($filename);
$fileReport = $this->linter->lintFile($filename, $report, $configuration, $logger);
$logger->notifyFileComplete($filename, $fileReport);
}
$logger->notifyRunComplete($report);
$exitCode = 0;
if ($report->countIssuesBySeverity(Issue::SEVERITY_ERROR) > 0) {
$exitCode = 2;
} elseif ($exitWithExitCode && $report->countIssues() > 0) {
$exitCode = 2;
}
$this->eventDispatcher->addListener(
ConsoleEvents::TERMINATE,
function (ConsoleTerminateEvent $event) use ($exitCode) {
$event->setExitCode($exitCode);
}
);
return $exitCode;
}
}
<?php declare(strict_types=1);
namespace Helmich\TypoScriptLint\Linter;
use Helmich\TypoScriptLint\Linter\Report\File;
use Helmich\TypoScriptLint\Linter\Report\Report;
use Helmich\TypoScriptLint\Logging\LinterLoggerInterface;
interface LinterInterface
{
/**
* @param string $filename
* @param Report $report
* @param LinterConfiguration $configuration
* @param LinterLoggerInterface $logger
* @return File
*/
public function lintFile(string $filename, Report $report, LinterConfiguration $configuration, LinterLoggerInterface $logger): File;
}
<?php declare(strict_types=1);
namespace Helmich\TypoScriptLint\Linter;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
class LinterConfiguration implements ConfigurationInterface
{
/** @var array */
private $configuration = [];
public function setConfiguration(array $configuration): void
{
$this->configuration = $configuration;
}
/**
* Get the list of paths to lint
*
* @return string[]
*/
public function getPaths(): array
{
$paths = [];
if (!empty($this->configuration['paths'])) {
$paths = $this->configuration['paths'];
}
return $paths;
}
/**
* Returns the list of supported filename patterns.
*
* @return string[]
*/
public function getFilePatterns(): array
{
return $this->configuration['filePatterns'] ?: [];
}
/**
* Returns the list of filename patterns that should be excluded even if supported.
*
* @return string[]
*/
public function getExcludePatterns(): array
{
return $this->configuration['excludePatterns'] ?: [];
}
/**
* @return array
*/
public function getSniffConfigurations(): array
{
$sniffs = [];
foreach ($this->configuration['sniffs'] as $class => $configuration) {
if (isset($configuration['disabled']) && $configuration['disabled']) {
continue;
}
$class = class_exists($class) ? $class : 'Helmich\\TypoScriptLint\\Linter\\Sniff\\' . $class . 'Sniff';
$configuration['class'] = $class;
$sniffs[] = $configuration;
}
return $sniffs;
}
/**
* Generates the configuration tree builder.
*
* @return TreeBuilder The tree builder
* @codeCoverageIgnore FU, I'm not going to test this one!
*
* @psalm-suppress TooManyArguments
* @psalm-suppress TooFewArguments
* @psalm-suppress UndefinedMethod
* @psalm-suppress DeprecatedMethod
* @noinspection PhpUndefinedMethodInspection
* @noinspection PhpParamsInspection
*/
public function getConfigTreeBuilder(): TreeBuilder
{
if (method_exists(TreeBuilder::class, 'getRootNode')) {
$treeBuilder = new TreeBuilder('typoscript-lint');
$root = $treeBuilder->getRootNode();
} else {
$treeBuilder = new TreeBuilder();
$root = $treeBuilder->root('typoscript-lint');
}
/** @psalm-suppress PossiblyUndefinedMethod */
$root
->children()
->arrayNode('paths')
->prototype('scalar')->end()
->end()
->arrayNode('filePatterns')
->prototype('scalar')->end()
->end()
->arrayNode('excludePatterns')
->prototype('scalar')->end()
->end()
->arrayNode('sniffs')
->isRequired()
->useAttributeAsKey('class')
->prototype('array')
->children()
->scalarNode('class')->end()
->variableNode('parameters')->end()
->booleanNode('disabled')->defaultValue(false)->end()
->end()
->end()
->end();
return $treeBuilder;
}
}
<?php declare(strict_types=1);
namespace Helmich\TypoScriptLint\Linter\ReportPrinter;
use Helmich\TypoScriptLint\Linter\Report\Report;
/**
* Interface definition for code linting report printers.
*
* @package Helmich\TypoScriptLint
* @subpcakage Linter\ReportPrinter
*/
interface Printer
{
/**
* Writes a report.
*
* @param Report $report
* @return void
*/
public function writeReport(Report $report): void;
}
<?php declare(strict_types=1);
namespace Helmich\TypoScriptLint\Linter\ReportPrinter;
use DOMDocument;
use Helmich\TypoScriptLint\Application;
use Helmich\TypoScriptLint\Linter\Report\Report;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Report printer that generates XML documents as generated by checkstyle [1].
*
* These reports are well-suited for being used in continuous integration
* environments like Jenkins [2].
*
* [1] http://checkstyle.sourceforge.net/
* [2] http://jenkins-ci.org/
*
* @package Helmich\TypoScriptLint
* @subpackage Linter\ReportPrinter
*/
class CheckstyleReportPrinter implements Printer
{
/** @var OutputInterface */
private $output;
/**
* Constructs a new checkstyle report printer.
*
* @param OutputInterface $output Output stream to write on. Might be STDOUT or a file.
*/
public function __construct(OutputInterface $output)
{
$this->output = $output;
}
/**
* Writes a report in checkstyle XML format.
*
* @param Report $report The report to print.
* @return void
*/
public function writeReport(Report $report): void
{
$xml = new DOMDocument('1.0', 'UTF-8');
$root = $xml->createElement('checkstyle');
$root->setAttribute('version', Application::APP_NAME . '-' . Application::APP_VERSION);
foreach ($report->getFiles() as $file) {
$xmlFile = $xml->createElement('file');
$xmlFile->setAttribute('name', $file->getFilename());
foreach ($file->getIssues() as $issue) {
$xmlWarning = $xml->createElement('error');
$xmlWarning->setAttribute('line', $issue->getLine() ? ((string) $issue->getLine()) : "");
$xmlWarning->setAttribute('severity', $issue->getSeverity());
$xmlWarning->setAttribute('message', $issue->getMessage());
$xmlWarning->setAttribute('source', $issue->getSource());
$column = $issue->getColumn();
if ($column !== null) {
$xmlWarning->setAttribute('column', "" . $column);
}
$xmlFile->appendChild($xmlWarning);
}
$root->appendChild($xmlFile);
}
$xml->appendChild($root);
$xml->formatOutput = true;
$this->output->write($xml->saveXML());
}
}
<?php declare(strict_types=1);
namespace Helmich\TypoScriptLint\Linter\ReportPrinter;
use Helmich\TypoScriptLint\Linter\Report\Report;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Report printer that generates an error report according to the GNU Coding Standards [1].
*
* [1] https://www.gnu.org/prep/standards/html_node/Errors.html
*
* @author Stefan Szymanski <stefan.szymanski@posteo.de>
* @license MIT
* @package Helmich\TypoScriptLint
* @subpackage Linter\ReportPrinter
*/
class GccReportPrinter implements Printer
{
/** @var OutputInterface */
private $output;
/**
* Constructs a new GCC report printer.
*
* @param OutputInterface $output Output stream to write on. Might be STDOUT or a file.
*/
public function __construct(OutputInterface $output)
{
$this->output = $output;
}
/**
* Writes a report in GCC format.
*
* @param Report $report The report to print.
* @return void
*/
public function writeReport(Report $report): void
{
foreach ($report->getFiles() as $file) {
foreach ($file->getIssues() as $issue) {
$this->output->writeLn(sprintf(
"%s:%d:%d: %s: %s",
$file->getFilename(),
$issue->getLine() ?: 0,
$issue->getColumn() ?: 0,
$issue->getSeverity(),
$issue->getMessage()
));
}
}
}
}
<?php declare(strict_types=1);
namespace Helmich\TypoScriptLint\Linter\ReportPrinter;
use Helmich\TypoScriptLint\Linter\Report\Report;
use Helmich\TypoScriptLint\Linter\Report\Issue;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Report printer that prints a report in human-readable form.
*
* These reports are useful for manual application. For generating
* machine-readable output, have a look at the CheckstyleReportPrinter
* class.
*
* @package Helmich\TypoScriptLint
* @subpackage Linter\ReportPrinter
*/
class ConsoleReportPrinter implements Printer
{
/** @var OutputInterface */
private $output;
/**
* Constructs a new console report printer.
*
* @param OutputInterface $output The output stream to write on.
*/
public function __construct(OutputInterface $output)
{
$this->output = $output;
}
/**
* Writes a report in human-readable table form.
*
* @param Report $report The report to print.
* @return void
*/
public function writeReport(Report $report): void
{
$count = 0;
$this->output->writeln('');
$this->output->writeln('<comment>CHECKSTYLE REPORT</comment>');
$styleMap = [
Issue::SEVERITY_ERROR => 'error',
Issue::SEVERITY_WARNING => 'comment',
Issue::SEVERITY_INFO => 'info',
];
foreach ($report->getFiles() as $file) {
$this->output->writeln("=> <comment>{$file->getFilename()}</comment>.");
foreach ($file->getIssues() as $issue) {
$count++;
$style = $styleMap[$issue->getSeverity()];
$this->output->writeln(
sprintf(
'<comment>%4d <%s>%s</%s></comment>',
$issue->getLine() ?? 0,
$style,
$issue->getMessage(),
$style
)
);
}
}
$summary = [];
foreach ($styleMap as $severity => $style) {
$severityCount = $report->countIssuesBySeverity($severity);
if ($severityCount > 0) {
$summary[] = "<comment>$severityCount</comment> {$severity}s";
}
}
$this->output->writeln("");
$this->output->writeln('<comment>SUMMARY</comment>');
$this->output->write("<info><comment>$count</comment> issues in total.</info>");
if ($count > 0) {
$this->output->writeln(" (" . implode(', ', $summary) . ")");
}
}
}
<?php declare(strict_types=1);
namespace Helmich\TypoScriptLint\Linter\Report;
use Helmich\TypoScriptParser\Parser\ParseError;
use Helmich\TypoScriptParser\Tokenizer\TokenizerException;
/**
* A single checkstyle warning.
*
* @author Martin Helmich <typo3@martin-helmich.de>
* @license MIT
* @package Helmich\TypoScriptLint
* @subpackage Linter\Report
*/
class Issue
{
const SEVERITY_INFO = "info";
const SEVERITY_WARNING = "warning";
const SEVERITY_ERROR = "error";
/** @var int|null */
private $line;
/** @var int|null */
private $column;
/** @var string */
private $message;
/** @var string */
private $severity;
/** @var string */
private $source;
/**
* Creates a new warning from a parse error.
*
* @param ParseError $parseError The parse error to convert into a warning.
* @return Issue The converted warning.
*/
public static function createFromParseError(ParseError $parseError): self
{
return new self(
$parseError->getSourceLine(),
0,
"Parse error: " . $parseError->getMessage(),
self::SEVERITY_ERROR,
get_class($parseError)
);
}
/**
* Creates a new warning from a tokenizer error.
*
* @param TokenizerException $tokenizerException The tokenizer error to convert into a warning.
* @return Issue The converted warning.
*/
public static function createFromTokenizerError(TokenizerException $tokenizerException): self
{
return new self(
$tokenizerException->getSourceLine(),
0,
"Tokenization error: " . $tokenizerException->getMessage(),
self::SEVERITY_ERROR,
get_class($tokenizerException)
);
}
/**
* Constructs a new warning.
*
* @param int|null $line The original source line the warning belongs to.
* @param int|null $column The source column.
* @param string $message The warning message.
* @param string $severity The warning severity (see Issue::SEVERITY_* constants).
* @param string $source An arbitrary identifier for the generator of this warning.
*/
public function __construct(?int $line, ?int $column, string $message, string $severity, string $source)
{
$this->line = $line;
$this->column = $column;
$this->message = $message;
$this->severity = $severity;
$this->source = $source;
}
/**
* Gets the original source line.
*
* @return int|null The original source line.
*/
public function getLine(): ?int
{
return $this->line;
}
/**
* Gets the original source column, if applicable (else NULL).
*
* @return int|null The original source column, or NULL.
*/
public function getColumn(): ?int
{
return $this->column;
}
/**
* Gets the warning message.
*
* @return string The warning message.
*/
public function getMessage(): string
{
return $this->message;
}
/**
* Gets the warning severity.
*
* @return string The warning severity (should be one of the Issue::SEVERITY_* constants).
*/
public function getSeverity(): string
{
return $this->severity;
}
/**
* Gets the warning source identifier.
*
* @return string The warning source identifier.
*/
public function getSource(): string
{
return $this->source;
}
}
<?php declare(strict_types=1);
namespace Helmich\TypoScriptLint\Linter\Report;
/**
* Checkstyle report containing issues for a single TypoScript file.
*
* @author Martin Helmich <typo3@martin-helmich.de>
* @license MIT
* @package Helmich\TypoScriptLint
* @subpackage Linter\Report
*/
class File
{
/** @var string */
private $filename;
/** @var Issue[] */
private $issues = [];
/**
* Constructs a new file report.
*
* @param string $filename The filename.
*/
public function __construct(string $filename)
{
$this->filename = $filename;
}
/**
* Gets the filename.
*
* @return string The filename.
*/
public function getFilename(): string
{
return $this->filename;
}
/**
* Adds a new issue for this file.
*
* @param Issue $issue The new issue
* @return void
*/
public function addIssue(Issue $issue): void
{
$this->issues[] = $issue;
}
/**
* Gets all issues for this file. The issues will be sorted by line
* numbers, not by order of addition to this report.
*
* @return Issue[] The issues for this file.
*/
public function getIssues(): array
{
usort(
$this->issues,
function (Issue $a, Issue $b): int {
return ($a->getLine() ?? 0) - ($b->getLine() ?? 0);
}
);
return $this->issues;
}
/**
* Gets all issues for this file that have a certain severity.
*
* @param string $severity The severity. Should be one of the Issue class' SEVERITY_* constants
* @return Issue[] All issues with the given severity
*/
public function getIssuesBySeverity(string $severity): array
{
return array_values(array_filter($this->getIssues(), function(Issue $i) use ($severity): bool {
return $i->getSeverity() === $severity;
}));
}
/**
* Creates a new empty report for the same file
*
* @return File The new report
*/
public function cloneEmpty(): self
{
return new self($this->filename);
}
/**
* Merges this file report with another file report
*
* @param File $other The file report to merge this report with
* @return File The merged report
*/
public function merge(File $other): self
{
$new = new self($this->filename);
$new->issues = array_merge($this->issues, $other->issues);
return $new;
}
}
<?php declare(strict_types=1);
namespace Helmich\TypoScriptLint\Linter\Report;
/**
* Checkstyle report for an entire set of files.
*
* @author Martin Helmich <typo3@martin-helmich.de>
* @license MIT
* @package Helmich\TypoScriptLint
* @subpackage Linter\Report
*/
class Report
{
/** @var File[] */
private $files = [];
/**
* Adds a sub-report for a specific file.
*
* @param File $file The file sub-report.
* @return void
*/
public function addFile(File $file): void
{
$this->files[] = $file;
}
/**
* Returns all file reports.
*
* @return File[] All file reports.
*/
public function getFiles(): array
{
return $this->files;
}
/**
* Returns the number of issues for the entire report.
*
* @return int The number of issues for the entire report.
*/
public function countIssues(): int
{
$count = 0;
foreach ($this->files as $file) {
$count += count($file->getIssues());
}
return $count;
}
/**
* Returns the number of issues with a given severity for the entire report
*
* @param string $severity The severity. Should be one of the Issue class' SEVERITY_* constants
* @return int The number of issues for the entire report.
*/
public function countIssuesBySeverity(string $severity): int
{
$count = 0;
foreach ($this->files as $file) {
$count += count($file->getIssuesBySeverity($severity));
}
return $count;
}
}
<?php declare(strict_types=1);
namespace Helmich\TypoScriptLint\Linter\Configuration;
use Helmich\TypoScriptLint\Linter\LinterConfiguration;
use Symfony\Component\Config\Definition\Processor;
use Symfony\Component\Config\Loader\LoaderInterface;
/**
* Helper class that loads linting configuration data.
*
* @author Martin Helmich <typo3@martin-helmich.de>
* @license MIT
* @package Helmich\TypoScriptLint
* @subpackage Linter\Configuration
*/
class ConfigurationLocator
{
/** @var LoaderInterface */
private $loader;
/** @var Processor */
private $processor;
/**
* Constructs a new configuration locator.
*
* @param LoaderInterface $loader A configuration loader.
* @param Processor $processor A configuration processor.
*/
public function __construct(LoaderInterface $loader, Processor $processor)
{
$this->loader = $loader;
$this->processor = $processor;
}
/**
* Loads the linter configuration.
*
* @param string[] $possibleConfigurationFiles A list of possible configuration files to load from. These
* files will be searched in the current working directory
* and in the typoscript-lint root directory. Contents from
* these files will also be merged with the
* typoscript-lint.dist.yml file in the typoscript-lint root
* directory.
* @param LinterConfiguration|null $configuration The configuration on which to set the loaded configuration values.
* @return LinterConfiguration The linter configuration from the given configuration file.
*/
public function loadConfiguration(array $possibleConfigurationFiles = [], ?LinterConfiguration $configuration = null): LinterConfiguration
{
$configs = [$this->loader->load('typoscript-lint.dist.yml')];
foreach ($possibleConfigurationFiles as $configurationFile) {
$loadedConfig = $this->loader->load($configurationFile);
// Simple mechanism to detect tslint config files ("ts" as in "TypeScript", not "Typoscript")
// and excluding them from being loaded.
if (isset($loadedConfig["extends"]) || isset($loadedConfig["rulesDirectory"]) || isset($loadedConfig["rules"])) {
continue;
}
$configs[] = $loadedConfig;
}
$configuration = $configuration ?? new LinterConfiguration();
$processedConfiguration = $this->processor->processConfiguration(
$configuration,
$configs
);
$configuration->setConfiguration($processedConfiguration);
return $configuration;
}
}
<?php declare(strict_types=1);
namespace Helmich\TypoScriptLint\Linter\Configuration;
use Helmich\TypoScriptLint\Util\Filesystem;
use Symfony\Component\Config\Exception\FileLocatorFileNotFoundException;
use Symfony\Component\Config\FileLocatorInterface;
use Symfony\Component\Config\Loader\FileLoader;
use Symfony\Component\Yaml\Parser as YamlParser;
/**
* Configuration loader for YAML files.
*
* @author Martin Helmich <typo3@martin-helmich.de>
* @license MIT
* @package Helmich\TypoScriptLint
* @subpackage Linter\Configuration
*
* @psalm-suppress MethodSignatureMismatch
*/
class YamlConfigurationLoader extends FileLoader
{
/** @var YamlParser */
private $yamlParser;
/** @var Filesystem */
private $filesystem;
/**
* Constructs a new YAML-based configuration loader.
*
* @param FileLocatorInterface $locator The file locator.
* @param YamlParser $yamlParser The YAML parser.
* @param Filesystem $filesystem A filesystem interface.
*/
public function __construct(FileLocatorInterface $locator, YamlParser $yamlParser, Filesystem $filesystem)
{
parent::__construct($locator);
$this->yamlParser = $yamlParser;
$this->filesystem = $filesystem;
}
/**
* Loads a resource.
*
* @param mixed $resource The resource
* @param string $type The resource type
* @return array
*
* @psalm-suppress MethodSignatureMismatch
*/
public function load($resource, $type = null): array
{
try {
/** @var string $path */
$path = $this->locator->locate($resource);
$file = $this->filesystem->openFile($path);
return $this->yamlParser->parse($file->getContents());
} catch (FileLocatorFileNotFoundException $error) {
return [];
}
}
/**
* Returns true if this class supports the given resource.
*
* @param mixed $resource A resource
* @param string $type The resource type
* @return bool true if this class supports the given resource, false otherwise
*
* @psalm-suppress MethodSignatureMismatch
*/
public function supports($resource, $type = null): bool
{
return is_string($resource) &&
in_array(pathinfo($resource, PATHINFO_EXTENSION), ['yml', 'yaml']);
}
}
<?php declare(strict_types=1);
namespace Helmich\TypoScriptLint\Linter\Sniff;
use Exception;
use Helmich\TypoScriptLint\Linter\LinterConfiguration;
class SniffLocator
{
/** @var SniffInterface[]|null */
private $sniffs = null;
/**
* @param LinterConfiguration $configuration
* @return SniffInterface[]
* @throws Exception
*
* @psalm-return array<int, SniffInterface>
*/
private function loadSniffs(LinterConfiguration $configuration): array
{
if ($this->sniffs !== null) {
return $this->sniffs;
}
$this->sniffs = [];
foreach ($configuration->getSniffConfigurations() as $sniffConfiguration) {
if (!class_exists($sniffConfiguration['class'])) {
throw new Exception(
'Class "' . $sniffConfiguration['class'] . '" could not be loaded!', 1402948667
);
}
$parameters = isset($sniffConfiguration['parameters']) ? $sniffConfiguration['parameters'] : [];
/** @var SniffInterface $sniff */
$sniff = new $sniffConfiguration['class']($parameters);
$this->sniffs[] = $sniff;
}
return $this->sniffs;
}
/**
* @param LinterConfiguration $configuration
* @return TokenStreamSniffInterface[]
* @throws Exception
*/
public function getTokenStreamSniffs(LinterConfiguration $configuration): array
{
$sniffs = $this->loadSniffs($configuration);
$tokenSniffs = [];
foreach ($sniffs as $sniff) {
if ($sniff instanceof TokenStreamSniffInterface) {
$tokenSniffs[] = $sniff;
}
}
return $tokenSniffs;
}
/**
* @param LinterConfiguration $configuration
* @return SyntaxTreeSniffInterface[]
* @throws Exception
*/
public function getSyntaxTreeSniffs(LinterConfiguration $configuration): array
{
$sniffs = $this->loadSniffs($configuration);
$tokenSniffs = [];
foreach ($sniffs as $sniff) {
if ($sniff instanceof SyntaxTreeSniffInterface) {
$tokenSniffs[] = $sniff;
}
}
return $tokenSniffs;
}
}
<?php declare(strict_types=1);
namespace Helmich\TypoScriptLint\Linter\Sniff\Visitor;
use Helmich\TypoScriptLint\Linter\Report\Issue;
use Helmich\TypoScriptParser\Parser\Traverser\Visitor;
interface SniffVisitor extends Visitor
{
/**
* @return Issue[]
*/
public function getIssues(): array;
}<?php declare(strict_types=1);
namespace Helmich\TypoScriptLint\Linter\Sniff\Visitor;
use Helmich\TypoScriptLint\Linter\Sniff\EmptySectionSniff;
use Helmich\TypoScriptLint\Linter\Report\Issue;
use Helmich\TypoScriptParser\Parser\AST\NestedAssignment;
use Helmich\TypoScriptParser\Parser\AST\Statement;
class EmptySectionVisitor implements SniffVisitor
{
/** @var Issue[] */
private $issues = [];
/**
* @return Issue[]
*/
public function getIssues(): array
{
return $this->issues;
}
public function enterTree(array $statements): void
{
}
public function enterNode(Statement $statement): void
{
if (!($statement instanceof NestedAssignment)) {
return;
}
if (count($statement->statements) === 0) {
$this->issues[] = new Issue(
$statement->sourceLine,
null,
"Empty assignment block",
Issue::SEVERITY_WARNING,
EmptySectionSniff::class
);
}
}
public function exitNode(Statement $statement): void
{
}
public function exitTree(array $statements): void
{
}
}<?php declare(strict_types=1);
namespace Helmich\TypoScriptLint\Linter\Sniff\Visitor;
use Helmich\TypoScriptLint\Linter\Report\Issue;
use Helmich\TypoScriptLint\Linter\Sniff\ConfigNoCacheSniff;
use Helmich\TypoScriptParser\Parser\AST\Operator\Assignment;
use Helmich\TypoScriptParser\Parser\AST\Statement;
class ConfigNoCacheVisitor implements SniffVisitor
{
/** @var Issue[] */
private $issues = [];
/**
* @return Issue[]
*/
public function getIssues(): array
{
return $this->issues;
}
public function enterTree(array $statements): void
{
}
public function enterNode(Statement $statement): void
{
if (!$statement instanceof Assignment) {
return;
}
if ($statement->object->relativeName !== 'no_cache'
&& substr($statement->object->relativeName, -9) !== '.no_cache') {
return;
}
if ($statement->value->value !== '0') {
$this->issues[] = new Issue(
$statement->sourceLine,
null,
sprintf(
'Setting config.no_cache = 1 is discouraged as it is bad for performance. '
. 'Consider using USER_INT object instead. Found in path: %s',
$statement->object->absoluteName
),
Issue::SEVERITY_WARNING,
ConfigNoCacheSniff::class
);
}
}
public function exitNode(Statement $statement): void
{
}
public function exitTree(array $statements): void
{
}
}
<?php declare(strict_types=1);
namespace Helmich\TypoScriptLint\Linter\Sniff\Visitor;
use Helmich\TypoScriptLint\Linter\Report\Issue;
use Helmich\TypoScriptLint\Linter\Sniff\NestingConsistencySniff;
use Helmich\TypoScriptParser\Parser\AST\ConditionalStatement;
use Helmich\TypoScriptParser\Parser\AST\NestedAssignment;
use Helmich\TypoScriptParser\Parser\AST\Operator\Assignment;
use Helmich\TypoScriptParser\Parser\AST\Statement;
class NestingConsistencyVisitor implements SniffVisitor
{
/** @var Issue[] */
private $issues = [];
/** @var integer */
private $commonPathPrefixThreshold;
public function __construct(int $commonPathPrefixThreshold = 1)
{
$this->commonPathPrefixThreshold = $commonPathPrefixThreshold;
}
/**
* @return Issue[]
*/
public function getIssues(): array
{
return $this->issues;
}
public function enterTree(array $statements): void
{
$this->walkStatementList($statements);
}
public function enterNode(Statement $statement): void
{
if ($statement instanceof NestedAssignment) {
$this->walkStatementList($statement->statements);
} elseif ($statement instanceof ConditionalStatement) {
$this->walkStatementList($statement->ifStatements);
$this->walkStatementList($statement->elseStatements);
}
}
public function exitNode(Statement $statement): void
{
}
public function exitTree(array $statements): void
{
}
/**
* @param Statement[] $statements
*/
private function walkStatementList(array $statements): void
{
list($knownObjectPaths, $knownNestedObjectPaths) = $this->getAssignedObjectPathsFromStatementList($statements);
// Step 2: Discover all plain assignments and determine whether any of them
// can be moved within one of the nested assignments.
foreach ($statements as $statement) {
if ($statement instanceof Assignment || $statement instanceof NestedAssignment) {
$commonPrefixWarnings = [];
foreach ($this->getParentObjectPathsForObjectPath($statement->object->relativeName) as $possibleObjectPath) {
if (isset($knownNestedObjectPaths[$possibleObjectPath])) {
$this->issues[] = new Issue(
$statement->sourceLine,
null,
sprintf(
'Assignment to value "%s", altough nested statement for path "%s" exists at line %d.',
$statement->object->relativeName,
$possibleObjectPath,
$knownNestedObjectPaths[$possibleObjectPath]
),
Issue::SEVERITY_WARNING,
NestingConsistencySniff::class
);
}
$assignmentsWithCommonPrefix = [];
foreach ($knownObjectPaths as $key => $line) {
$key = "" . $key;
if ($key !== $statement->object->relativeName && strpos($key, $possibleObjectPath . '.') === 0) {
if (!isset($assignmentsWithCommonPrefix[$key])) {
$assignmentsWithCommonPrefix[$key] = [];
}
$assignmentsWithCommonPrefix[$possibleObjectPath][] = [$key, $line];
}
}
foreach ($assignmentsWithCommonPrefix as $commonPrefix => $lines) {
if (count($lines) < $this->commonPathPrefixThreshold) {
continue;
}
$descr = [];
foreach ($lines as $l) {
$descr[] = sprintf('"%s" in line %d', $l[0], $l[1]);
}
$commonPrefixWarnings[$commonPrefix] = new Issue(
$statement->sourceLine,
null,
sprintf(
'Common path prefix "%s" with %s to %s. Consider merging them into a nested assignment.',
$commonPrefix,
count($lines) === 1 ? 'assignment' : 'assignments',
implode(", ", $descr)
),
Issue::SEVERITY_WARNING,
NestingConsistencySniff::class
);
}
}
$this->issues = array_merge($this->issues, array_values($commonPrefixWarnings));
}
}
}
/**
* @param string $objectPath
* @return string[]
*/
private function getParentObjectPathsForObjectPath(string $objectPath): array
{
$components = preg_split('/(?<!\\\)\./', $objectPath);
$paths = [];
for ($i = 1; $i < count($components); $i++) {
$possibleObjectPath = implode('.', array_slice($components, 0, $i));
$paths[] = $possibleObjectPath;
}
return $paths;
}
/**
* @param Statement[] $statements
*
* @return int[][]
*
* @phan-return array{0:array<string|int,string>,1:array<string|int,string>}
*
* @psalm-return array{0: array<string, int>, 1: array<string, int>}
*/
private function getAssignedObjectPathsFromStatementList(array $statements): array
{
$knownObjectPaths = [];
$knownNestedObjectPaths = [];
// Step 1: Discover all nested object assignment statements.
foreach ($statements as $statement) {
if ($statement instanceof Assignment || $statement instanceof NestedAssignment) {
$knownObjectPaths[$statement->object->relativeName] = $statement->sourceLine;
if ($statement instanceof NestedAssignment) {
if (isset($knownNestedObjectPaths[$statement->object->relativeName])) {
$this->issues[] = new Issue(
$statement->sourceLine,
null,
sprintf(
'Multiple nested statements for object path "%s". Consider merging them into one statement.',
$statement->object->relativeName
),
Issue::SEVERITY_WARNING,
NestingConsistencySniff::class
);
} else {
$knownNestedObjectPaths[$statement->object->relativeName] = $statement->sourceLine;
}
}
}
}
return [$knownObjectPaths, $knownNestedObjectPaths];
}
}
<?php declare(strict_types=1);
namespace Helmich\TypoScriptLint\Linter\Sniff\Visitor;
use Helmich\TypoScriptLint\Linter\Report\Issue;
use Helmich\TypoScriptLint\Linter\Sniff\DuplicateAssignmentSniff;
use Helmich\TypoScriptParser\Parser\AST\ConditionalStatement;
use Helmich\TypoScriptParser\Parser\AST\Operator\Assignment;
use Helmich\TypoScriptParser\Parser\AST\Statement;
class DuplicateAssignmentVisitor implements SniffVisitor
{
/** @var Assignment[] */
private $assignments = [];
/** @var Issue[] */
private $issues = [];
/** @var bool */
private $inCondition = false;
/**
* @return Issue[]
*/
public function getIssues(): array
{
return $this->issues;
}
public function enterTree(array $statements): void
{
}
public function enterNode(Statement $statement): void
{
if ($statement instanceof ConditionalStatement) {
$this->inCondition = true;
}
if ($statement instanceof Assignment && false === $this->inCondition) {
if (isset($this->assignments[$statement->object->absoluteName])) {
/** @var Statement $lastAssignment */
$lastAssignment = $this->assignments[$statement->object->absoluteName];
$this->issues[] = new Issue(
$lastAssignment->sourceLine,
null,
sprintf(
'Value of object "%s" is overwritten in line %d.',
$statement->object->absoluteName,
$statement->sourceLine
),
Issue::SEVERITY_WARNING,
DuplicateAssignmentSniff::class
);
}
$this->assignments[$statement->object->absoluteName] = $statement;
}
}
public function exitNode(Statement $statement): void
{
// Luckily, conditions cannot be nested. Phew.
if ($statement instanceof ConditionalStatement) {
$this->inCondition = false;
}
}
public function exitTree(array $statements): void
{
}
}
<?php declare(strict_types=1);
namespace Helmich\TypoScriptLint\Linter\Sniff;
use Helmich\TypoScriptLint\Linter\Sniff\Visitor\EmptySectionVisitor;
use Helmich\TypoScriptLint\Linter\Sniff\Visitor\SniffVisitor;
class EmptySectionSniff extends AbstractSyntaxTreeSniff
{
protected function buildVisitor(): SniffVisitor
{
return new EmptySectionVisitor();
}
}<?php declare(strict_types=1);
namespace Helmich\TypoScriptLint\Linter\Sniff;
use Helmich\TypoScriptLint\Linter\Sniff\Visitor\NestingConsistencyVisitor;
use Helmich\TypoScriptLint\Linter\Sniff\Visitor\SniffVisitor;
class NestingConsistencySniff extends AbstractSyntaxTreeSniff
{
/** @var int */
private $commonPathPrefixThreshold = 1;
public function __construct(array $parameters)
{
if (array_key_exists('commonPathPrefixThreshold', $parameters)) {
$this->commonPathPrefixThreshold = $parameters['commonPathPrefixThreshold'];
}
}
/**
* @return SniffVisitor
*/
protected function buildVisitor(): SniffVisitor
{
return new NestingConsistencyVisitor($this->commonPathPrefixThreshold);
}
}
<?php declare(strict_types=1);
namespace Helmich\TypoScriptLint\Linter\Sniff;
interface SniffInterface
{
/**
* @param array $parameters
*/
public function __construct(array $parameters);
}
<?php declare(strict_types=1);
namespace Helmich\TypoScriptLint\Linter\Sniff;
use Helmich\TypoScriptLint\Linter\LinterConfiguration;
use Helmich\TypoScriptLint\Linter\Report\File;
use Helmich\TypoScriptParser\Tokenizer\TokenInterface;
interface TokenStreamSniffInterface extends SniffInterface
{
/**
* @param TokenInterface[] $tokens
* @param File $file
* @param LinterConfiguration $configuration
* @return void
*/
public function sniff(array $tokens, File $file, LinterConfiguration $configuration): void;
}
<?php declare(strict_types=1);
namespace Helmich\TypoScriptLint\Linter\Sniff\Inspection;
use Helmich\TypoScriptParser\Tokenizer\TokenInterface;
/**
* Helper trait that contains common inspections for token streams
*
* @package Helmich\TypoScriptLint
* @subpackage Linter\Sniff\Inspection
*/
trait TokenInspections
{
/**
* Tests whether a token is an operator
*
* @param TokenInterface $token
* @return bool
*/
private static function isOperator(TokenInterface $token): bool
{
return static::isUnaryOperator($token) || static::isBinaryOperator($token);
}
/**
* Tests whether a token is a unary operator
*
* @param TokenInterface $token
* @return bool
*/
private static function isUnaryOperator(TokenInterface $token): bool
{
return $token->getType() === TokenInterface::TYPE_OPERATOR_DELETE;
}
/**
* Tests whether a token is a binary operator
*
* @param TokenInterface $token
* @return bool
*/
private static function isBinaryOperator(TokenInterface $token): bool
{
return in_array($token->getType(), [
TokenInterface::TYPE_OPERATOR_ASSIGNMENT,
TokenInterface::TYPE_OPERATOR_COPY,
TokenInterface::TYPE_OPERATOR_MODIFY,
TokenInterface::TYPE_OPERATOR_REFERENCE,
]);
}
/**
* Tests whether a token is a whitespace
*
* @param TokenInterface $token
* @return bool
*/
private static function isWhitespace(TokenInterface $token): bool
{
return $token->getType() === TokenInterface::TYPE_WHITESPACE;
}
/**
* Tests whether a token is a whitespace of a given length
*
* @param TokenInterface $token
* @param int $length
* @return bool
*/
private static function isWhitespaceOfLength(TokenInterface $token, int $length): bool
{
return static::isWhitespace($token) && strlen(trim($token->getValue(), "\n")) == $length;
}
}<?php declare(strict_types=1);
namespace Helmich\TypoScriptLint\Linter\Sniff;
use Helmich\TypoScriptLint\Linter\LinterConfiguration;
use Helmich\TypoScriptLint\Linter\Report\File;
use Helmich\TypoScriptLint\Linter\Report\Issue;
use Helmich\TypoScriptParser\Tokenizer\TokenInterface;
class RepeatingRValueSniff implements TokenStreamSniffInterface
{
const CONSTANT_EXPRESSION = ',\{\$[a-zA-Z0-9_\.]+\},';
/** @var array<string, int> */
private $knownRightValues = [];
/** @var string[] */
private $allowedRightValues = [];
/** @var int */
private $valueLengthThreshold = 8;
/**
* @param array $parameters
* @psalm-param array{allowedRightValues: ?string[], valueLengthThreshold: ?int} $parameters
* @psalm-suppress MoreSpecificImplementedParamType
*/
public function __construct(array $parameters)
{
if (isset($parameters["allowedRightValues"])) {
$this->allowedRightValues = $parameters["allowedRightValues"];
}
if (isset($parameters["valueLengthThreshold"])) {
$this->valueLengthThreshold = $parameters["valueLengthThreshold"];
}
}
/**
* @param TokenInterface[] $tokens
* @param File $file
* @param LinterConfiguration $configuration
* @return void
*/
public function sniff(array $tokens, File $file, LinterConfiguration $configuration): void
{
foreach ($tokens as $token) {
$isRValue = $token->getType() === TokenInterface::TYPE_RIGHTVALUE;
$valueIsLongerThanThreshold = strlen($token->getValue()) >= $this->valueLengthThreshold;
if (!$isRValue || !$valueIsLongerThanThreshold) {
continue;
}
if (preg_match(self::CONSTANT_EXPRESSION, $token->getValue())) {
continue;
}
if (in_array($token->getValue(), $this->allowedRightValues)) {
continue;
}
if (!array_key_exists($token->getValue(), $this->knownRightValues)) {
$this->knownRightValues[$token->getValue()] = 0;
}
$this->knownRightValues[$token->getValue()]++;
if ($this->knownRightValues[$token->getValue()] > 1) {
$file->addIssue(new Issue(
$token->getLine(),
null,
'Duplicated value "' . $token->getValue() . '". Consider extracting it into a constant.',
Issue::SEVERITY_WARNING,
__CLASS__
));
}
}
}
}
<?php declare(strict_types=1);
namespace Helmich\TypoScriptLint\Linter\Sniff;
use Helmich\TypoScriptLint\Linter\LinterConfiguration;
use Helmich\TypoScriptLint\Linter\Report\File;
use Helmich\TypoScriptParser\Parser\AST\Statement;
interface SyntaxTreeSniffInterface extends SniffInterface
{
/**
* @param Statement[] $statements
* @param File $file
* @param LinterConfiguration $configuration
* @return void
*/
public function sniff(array $statements, File $file, LinterConfiguration $configuration): void;
}
<?php declare(strict_types=1);
namespace Helmich\TypoScriptLint\Linter\Sniff;
use Helmich\TypoScriptLint\Linter\LinterConfiguration;
use Helmich\TypoScriptLint\Linter\Report\File;
use Helmich\TypoScriptLint\Linter\Report\Issue;
use Helmich\TypoScriptLint\Linter\Sniff\Inspection\TokenInspections;
use Helmich\TypoScriptParser\Tokenizer\LineGrouper;
use Helmich\TypoScriptParser\Tokenizer\TokenInterface;
class OperatorWhitespaceSniff implements TokenStreamSniffInterface
{
use TokenInspections;
/**
* @param array $parameters
*/
public function __construct(array $parameters)
{
}
/**
* @param TokenInterface[] $tokens
* @param File $file
* @param LinterConfiguration $configuration
* @return void
*/
public function sniff(array $tokens, File $file, LinterConfiguration $configuration): void
{
$tokensByLine = new LineGrouper($tokens);
/** @var TokenInterface[] $tokensInLine */
foreach ($tokensByLine->getLines() as $line => $tokensInLine) {
$count = count($tokensInLine);
for ($i = 0; $i < $count; $i++) {
if (!($tokensInLine[$i]->getType() === TokenInterface::TYPE_OBJECT_IDENTIFIER && isset($tokensInLine[$i + 1]))) {
continue;
}
if (!self::isWhitespace($tokensInLine[$i + 1])) {
$file->addIssue(new Issue(
$tokensInLine[$i]->getLine(),
null,
'No whitespace after object accessor.',
Issue::SEVERITY_WARNING,
__CLASS__
));
} elseif (!self::isWhitespaceOfLength($tokensInLine[$i + 1], 1)) {
$file->addIssue(new Issue(
$tokensInLine[$i]->getLine(),
null,
'Accessor should be followed by single space.',
Issue::SEVERITY_WARNING,
__CLASS__
));
}
// Scan forward until we find the actual operator
for ($j = 0; $j < $count && !self::isOperator($tokensInLine[$j]); $j ++);
if (isset($tokensInLine[$j + 1]) && isset($tokensInLine[$j + 2]) && self::isBinaryOperator($tokensInLine[$j])) {
if (!self::isWhitespace($tokensInLine[$j + 1])) {
$file->addIssue(new Issue(
$tokensInLine[$j]->getLine(),
null,
'No whitespace after operator.',
Issue::SEVERITY_WARNING,
__CLASS__
));
} elseif (!self::isWhitespaceOfLength($tokensInLine[$j + 1], 1)) {
$file->addIssue(new Issue(
$tokensInLine[$j]->getLine(),
null,
'Operator should be followed by single space.',
Issue::SEVERITY_WARNING,
__CLASS__
));
}
}
break;
}
}
}
}
<?php declare(strict_types=1);
namespace Helmich\TypoScriptLint\Linter\Sniff;
use Helmich\TypoScriptLint\Linter\Sniff\Visitor\DuplicateAssignmentVisitor;
use Helmich\TypoScriptLint\Linter\Sniff\Visitor\SniffVisitor;
class DuplicateAssignmentSniff extends AbstractSyntaxTreeSniff
{
/**
* @return SniffVisitor
*/
protected function buildVisitor(): SniffVisitor
{
return new DuplicateAssignmentVisitor();
}
}
<?php declare(strict_types=1);
namespace Helmich\TypoScriptLint\Linter\Sniff;
use Helmich\TypoScriptLint\Linter\LinterConfiguration;
use Helmich\TypoScriptLint\Linter\Report\File;
use Helmich\TypoScriptLint\Linter\Sniff\Visitor\SniffVisitor;
use Helmich\TypoScriptParser\Parser\AST\Statement;
use Helmich\TypoScriptParser\Parser\Traverser\Traverser;
/**
* Abstract base class for sniffs that inspect a file's syntax tree
*
* @package Helmich\TypoScriptLint
* @subpackage Linter\Sniff
*/
abstract class AbstractSyntaxTreeSniff implements SyntaxTreeSniffInterface
{
/**
* @param array $parameters
*/
public function __construct(array $parameters)
{
}
/**
* @param Statement[] $statements
* @param File $file
* @param LinterConfiguration $configuration
* @return void
*/
public function sniff(array $statements, File $file, LinterConfiguration $configuration): void
{
$visitor = $this->buildVisitor();
$traverser = new Traverser($statements);
$traverser->addVisitor($visitor);
$traverser->walk();
foreach ($visitor->getIssues() as $issue) {
$file->addIssue($issue);
}
}
/**
* @return SniffVisitor
*/
abstract protected function buildVisitor(): SniffVisitor;
}
<?php declare(strict_types=1);
namespace Helmich\TypoScriptLint\Linter\Sniff;
use Helmich\TypoScriptLint\Linter\LinterConfiguration;
use Helmich\TypoScriptLint\Linter\Report\File;
use Helmich\TypoScriptLint\Linter\Report\Issue;
use Helmich\TypoScriptParser\Parser\Parser;
use Helmich\TypoScriptParser\Tokenizer\TokenInterface;
use Helmich\TypoScriptParser\Tokenizer\Tokenizer;
class DeadCodeSniff implements TokenStreamSniffInterface
{
const ANNOTATION_COMMENT = '/^\s*([a-z0-9]+=(.*?))(;\s*[a-z0-9]+=(.*?))*\s*$/';
/**
* @param array $parameters
*/
public function __construct(array $parameters)
{
}
/**
* @param TokenInterface[] $tokens
* @param File $file
* @param LinterConfiguration $configuration
* @return void
*/
public function sniff(array $tokens, File $file, LinterConfiguration $configuration): void
{
foreach ($tokens as $token) {
if (!($token->getType() === TokenInterface::TYPE_COMMENT_ONELINE
|| $token->getType() === TokenInterface::TYPE_COMMENT_MULTILINE)) {
continue;
}
$commentContent = preg_replace(',^\s*(#|/\*|/)\s*,', '', $token->getValue());
if (preg_match(static::ANNOTATION_COMMENT, $commentContent)) {
continue;
} else if (preg_match(Tokenizer::TOKEN_OPERATOR_LINE, $commentContent, $matches)) {
try {
$parser = new Parser(new Tokenizer());
$parser->parseString($commentContent);
$file->addIssue(new Issue(
$token->getLine(),
0,
'Found commented code (' . $matches[0] . ').',
Issue::SEVERITY_INFO,
__CLASS__
));
} catch (\Exception $e) {
// pass
}
}
}
}
}
<?php declare(strict_types=1);
namespace Helmich\TypoScriptLint\Linter\Sniff;
use Helmich\TypoScriptLint\Linter\Sniff\Visitor\ConfigNoCacheVisitor;
use Helmich\TypoScriptLint\Linter\Sniff\Visitor\SniffVisitor;
class ConfigNoCacheSniff extends AbstractSyntaxTreeSniff {
/**
* @return SniffVisitor
*/
protected function buildVisitor(): SniffVisitor
{
return new ConfigNoCacheVisitor();
}
}
<?php declare(strict_types=1);
namespace Helmich\TypoScriptLint\Linter\Sniff;
use Helmich\TypoScriptLint\Linter\LinterConfiguration;
use Helmich\TypoScriptLint\Linter\Report\File;
use Helmich\TypoScriptLint\Linter\Report\Issue;
use Helmich\TypoScriptLint\Linter\Sniff\Inspection\TokenInspections;
use Helmich\TypoScriptParser\Tokenizer\LineGrouper;
use Helmich\TypoScriptParser\Tokenizer\TokenInterface;
class IndentationSniff implements TokenStreamSniffInterface
{
use TokenInspections;
/** @var bool */
private $useSpaces = true;
/** @var int */
private $indentPerLevel = 4;
/**
* Defines whether code inside conditions should be indented by one level.
*
* @var bool
*/
private $indentConditions = false;
/**
* Track whether we are inside a condition.
*
* @var bool
*/
private $insideCondition = false;
/**
* @param array $parameters
* @psalm-param array{useSpaces: ?bool, indentPerLevel: ?int, indentCondition: ?bool} $parameters
* @psalm-suppress MoreSpecificImplementedParamType
*/
public function __construct(array $parameters)
{
if (isset($parameters['useSpaces'])) {
$this->useSpaces = $parameters['useSpaces'];
}
if (isset($parameters['indentPerLevel'])) {
$this->indentPerLevel = $parameters['indentPerLevel'];
}
if (isset($parameters['indentConditions'])) {
$this->indentConditions = $parameters['indentConditions'];
}
}
/**
* @param TokenInterface[] $tokens
* @param File $file
* @param LinterConfiguration $configuration
* @return void
*/
public function sniff(array $tokens, File $file, LinterConfiguration $configuration): void
{
$indentCharacter = $this->useSpaces ? ' ' : "\t";
$tokensByLine = new LineGrouper($tokens);
$indentationLevel = 0;
/** @var TokenInterface[] $tokensInLine */
foreach ($tokensByLine->getLines() as $line => $tokensInLine) {
$indentationLevel = $this->reduceIndentationLevel($indentationLevel, $tokensInLine);
$expectedIndentationCharacterCount = $this->indentPerLevel * $indentationLevel;
$expectedIndentation = str_repeat(
$indentCharacter,
$expectedIndentationCharacterCount
);
$tokensInLine = array_values(array_filter($tokensInLine, function(TokenInterface $token): bool {
return $token->getType() !== TokenInterface::TYPE_RIGHTVALUE_MULTILINE;
}));
// Skip empty lines and conditions inside conditions.
if ($this->isEmptyLine($tokensInLine) || $this->insideCondition && $tokensInLine[0] !== TokenInterface::TYPE_CONDITION && !self::isWhitespace($tokensInLine[0])) {
continue;
}
$line = (int)$line;
if ($indentationLevel === 0 && self::isWhitespace($tokensInLine[0]) && strlen($tokensInLine[0]->getValue())) {
$file->addIssue($this->createIssue($line, $indentationLevel, $tokensInLine[0]->getValue()));
} elseif ($indentationLevel > 0) {
if (!self::isWhitespace($tokensInLine[0])) {
$file->addIssue($this->createIssue($line, $indentationLevel, ''));
} elseif ($tokensInLine[0]->getValue() !== $expectedIndentation) {
$file->addIssue($this->createIssue($line, $indentationLevel, $tokensInLine[0]->getValue()));
}
}
$indentationLevel = $this->raiseIndentationLevel($indentationLevel, $tokensInLine);
}
}
/**
* Checks if a stream of tokens is an empty line.
*
* @param TokenInterface[] $tokensInLine
* @return bool
*/
private function isEmptyLine(array $tokensInLine): bool
{
if (count($tokensInLine) > 1) {
return false;
}
$firstToken = $tokensInLine[0];
return $firstToken->getType() === TokenInterface::TYPE_WHITESPACE && $firstToken->getValue() === "\n";
}
/**
* Check whether indentation should be reduced by one level, for current line.
*
* Checks tokens in current line, and whether they will reduce the indentation by one.
*
* @param int $indentationLevel The current indentation level
* @param TokenInterface[] $tokensInLine
* @return int The new indentation level
*/
private function reduceIndentationLevel(int $indentationLevel, array $tokensInLine): int
{
$raisingIndentation = [
TokenInterface::TYPE_BRACE_CLOSE,
];
if ($this->indentConditions) {
$raisingIndentation[] = TokenInterface::TYPE_CONDITION_END;
}
foreach ($tokensInLine as $token) {
if (in_array($token->getType(), $raisingIndentation)) {
if ($token->getType() === TokenInterface::TYPE_CONDITION_END) {
$this->insideCondition = false;
}
return max($indentationLevel - 1, 0);
}
}
return $indentationLevel;
}
/**
* Check whether indentation should be raised by one level, for current line.
*
* Checks tokens in current line, and whether they will raise the indentation by one.
*
* @param int $indentationLevel The current indentation level
* @param TokenInterface[] $tokensInLine
* @return int The new indentation level
*/
private function raiseIndentationLevel(int $indentationLevel, array $tokensInLine): int
{
$raisingIndentation = [
TokenInterface::TYPE_BRACE_OPEN,
];
if ($this->indentConditions && $this->insideCondition === false) {
$raisingIndentation[] = TokenInterface::TYPE_CONDITION;
}
foreach ($tokensInLine as $token) {
if (in_array($token->getType(), $raisingIndentation)) {
if ($token->getType() === TokenInterface::TYPE_CONDITION) {
$this->insideCondition = true;
}
return $indentationLevel + 1;
}
}
return $indentationLevel;
}
private function createIssue(int $line, int $expectedLevel, string $actual): Issue
{
$indentCharacterCount = ($expectedLevel * $this->indentPerLevel);
$indentCharacterDescription = ($this->useSpaces ? 'space' : 'tab') . (($indentCharacterCount == 1) ? '' : 's');
$expectedMessage = "Expected indent of {$indentCharacterCount} {$indentCharacterDescription}.";
return new Issue($line, strlen($actual), $expectedMessage, Issue::SEVERITY_WARNING, __CLASS__);
}
}
<?php declare(strict_types=1);
namespace Helmich\TypoScriptLint\Linter;
use Helmich\TypoScriptLint\Linter\Report\File;
use Helmich\TypoScriptLint\Linter\Report\Issue;
use Helmich\TypoScriptLint\Linter\Report\Report;
use Helmich\TypoScriptLint\Linter\Sniff\SniffLocator;
use Helmich\TypoScriptLint\Logging\LinterLoggerInterface;
use Helmich\TypoScriptParser\Parser\AST\Statement;
use Helmich\TypoScriptParser\Parser\ParseError;
use Helmich\TypoScriptParser\Parser\ParserInterface;
use Helmich\TypoScriptParser\Tokenizer\TokenInterface;
use Helmich\TypoScriptParser\Tokenizer\TokenizerException;
use Helmich\TypoScriptParser\Tokenizer\TokenizerInterface;
class Linter implements LinterInterface
{
/** @var TokenizerInterface */
private $tokenizer;
/** @var ParserInterface */
private $parser;
/** @var SniffLocator */
private $sniffLocator;
public function __construct(TokenizerInterface $tokenizer, ParserInterface $parser, SniffLocator $sniffLocator)
{
$this->tokenizer = $tokenizer;
$this->parser = $parser;
$this->sniffLocator = $sniffLocator;
}
/**
* @param string $filename
* @param Report $report
* @param LinterConfiguration $configuration
* @param LinterLoggerInterface $logger
* @return File
*/
public function lintFile(string $filename, Report $report, LinterConfiguration $configuration, LinterLoggerInterface $logger): File
{
$file = new File($filename);
try {
$tokens = $this->tokenizer->tokenizeStream($filename);
$statements = $this->parser->parseTokens($tokens);
$file = $this->lintTokenStream($tokens, $file, $configuration, $logger);
$file = $this->lintSyntaxTree($statements, $file, $configuration, $logger);
} catch (TokenizerException $tokenizerException) {
$file->addIssue(Issue::createFromTokenizerError($tokenizerException));
} catch (ParseError $parseError) {
$file->addIssue(Issue::createFromParseError($parseError));
}
if (count($file->getIssues()) > 0) {
$report->addFile($file);
}
return $file;
}
/**
* @param TokenInterface[] $tokens
* @param File $file
* @param LinterConfiguration $configuration
* @param LinterLoggerInterface $logger
* @return File
*/
private function lintTokenStream(
array $tokens,
File $file,
LinterConfiguration $configuration,
LinterLoggerInterface $logger
): File {
$sniffs = $this->sniffLocator->getTokenStreamSniffs($configuration);
foreach ($sniffs as $sniff) {
$sniffReport = $file->cloneEmpty();
$logger->notifyFileSniffStart($file->getFilename(), get_class($sniff));
$sniff->sniff($tokens, $sniffReport, $configuration);
$file = $file->merge($sniffReport);
$logger->nofifyFileSniffComplete($file->getFilename(), get_class($sniff), $sniffReport);
}
return $file;
}
/**
* @param Statement[] $statements
* @param File $file
* @param LinterConfiguration $configuration
* @param LinterLoggerInterface $logger
* @return File
*/
private function lintSyntaxTree(
array $statements,
File $file,
LinterConfiguration $configuration,
LinterLoggerInterface $logger
): File {
$sniffs = $this->sniffLocator->getSyntaxTreeSniffs($configuration);
foreach ($sniffs as $sniff) {
$sniffReport = $file->cloneEmpty();
$logger->notifyFileSniffStart($file->getFilename(), get_class($sniff));
$sniff->sniff($statements, $sniffReport, $configuration);
$file = $file->merge($sniffReport);
$logger->nofifyFileSniffComplete($file->getFilename(), get_class($sniff), $sniffReport);
}
return $file;
}
}
<?php declare(strict_types=1);
namespace Helmich\TypoScriptLint\Exception;
class BadOutputFileException extends \Exception
{
}
<?php declare(strict_types=1);
namespace Helmich\TypoScriptLint\Logging;
use Helmich\TypoScriptLint\Linter\ReportPrinter\CheckstyleReportPrinter;
use Helmich\TypoScriptLint\Linter\ReportPrinter\ConsoleReportPrinter;
use Helmich\TypoScriptLint\Linter\ReportPrinter\GccReportPrinter;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Helper class responsible for building a logger based on given input parameters
*
* @author Martin Helmich <typo3@martin-helmich.de>
* @license MIT
* @package Helmich\TypoScriptLint
* @subpackage Logging
*/
class LinterLoggerBuilder
{
/**
* Builds a suitable logger for logging lint progress and results.
*
* @param string $outputFormat The desired output format, as specified by the user, e.g. via command-line parameter
* @param OutputInterface $reportOutput Output stream for the result report (usually STDOUT or a file)
* @param OutputInterface $consoleOutput Output stream for console data (usually STDOUT)
* @return LinterLoggerInterface The printer matching the user's specifications.
*/
public function createLogger(string $outputFormat, OutputInterface $reportOutput, OutputInterface $consoleOutput): LinterLoggerInterface
{
$errorOutput = ($consoleOutput instanceof ConsoleOutputInterface)
? $consoleOutput->getErrorOutput()
: $consoleOutput;
switch ($outputFormat) {
case 'checkstyle':
case 'xml':
return new CompactConsoleLogger(new CheckstyleReportPrinter($reportOutput), $errorOutput);
case 'txt':
case 'text':
return new VerboseConsoleLogger(new ConsoleReportPrinter($reportOutput), $consoleOutput);
case 'compact':
return new CompactConsoleLogger(new ConsoleReportPrinter($reportOutput), $consoleOutput);
case 'gcc':
return new MinimalConsoleLogger(new GccReportPrinter($reportOutput), $errorOutput);
default:
throw new \InvalidArgumentException('Invalid report printer "' . $outputFormat . '"!');
}
}
}
<?php declare(strict_types=1);
namespace Helmich\TypoScriptLint\Logging;
use Helmich\TypoScriptLint\Linter\Report\File;
use Helmich\TypoScriptLint\Linter\Report\Report;
/**
* Interface definition for a progress logger
*
* This logger is domain-specific, and implements methods for very specific
* domain events. This way, how to treat specific events is entirely up to the
* logger implementation.
*
* @author Martin Helmich <typo3@martin-helmich.de>
* @license MIT
* @package Helmich\TypoScriptLint
* @subpackage Logging
*/
interface LinterLoggerInterface
{
/**
* Called when a desired input directory/file does not exist
*
* @param string $file
* @return void
*/
public function notifyFileNotFound(string $file): void;
/**
* Called before linting any input file
*
* @param string[] $files The list of filenames to lint
* @return void
*/
public function notifyFiles(array $files): void;
/**
* Called before linting any specific file
*
* @param string $filename The name of the file to be linted
* @return void
*/
public function notifyFileStart(string $filename): void;
/**
* Called before running a specific sniff on a file
*
* @param string $filename The name of the file to be linted
* @param string $sniffClass The class name of the sniff to be run
* @return void
*/
public function notifyFileSniffStart(string $filename, string $sniffClass): void;
/**
* Called after completing a specific sniff on a file
*
* @param string $filename The name of the file that was linted
* @param string $sniffClass The class name of the sniff that was run
* @param File $report The (preliminary) linting report for this file
* @return void
*/
public function nofifyFileSniffComplete(string $filename, string $sniffClass, File $report): void;
/**
* Called after completing all sniffs on a file
*
* @param string $filename The name of the file that was linted
* @param File $report The (final) linting report for this file
* @return void
*/
public function notifyFileComplete(string $filename, File $report): void;
/**
* Called after all files have been linted
*
* @param Report $report The final linting report for all files
* @return void
*/
public function notifyRunComplete(Report $report): void;
}<?php declare(strict_types=1);
namespace Helmich\TypoScriptLint\Logging;
use Helmich\TypoScriptLint\Linter\Report\File;
use Helmich\TypoScriptLint\Linter\Report\Report;
use Helmich\TypoScriptLint\Linter\ReportPrinter\Printer;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Minimal console logger
*
* This logger prints only the issues to the console.
*
* @author Stefan Szymanski <stefan.szymanski@posteo.de>
* @license MIT
* @package Helmich\TypoScriptLint
* @subpackage Logging
*/
class MinimalConsoleLogger implements LinterLoggerInterface
{
/** @var Printer */
private $printer;
/** @var OutputInterface */
private $output;
public function __construct(Printer $printer, OutputInterface $output)
{
$this->printer = $printer;
$this->output = $output;
}
public function notifyFileNotFound(string $file): void
{
$this->output->writeln("<error>WARNING: Input file ${file} does not seem to exist.</error>");
}
public function notifyFiles(array $files): void
{
}
public function notifyFileStart(string $filename): void
{
}
public function notifyFileSniffStart(string $filename, string $sniffClass): void
{
}
public function nofifyFileSniffComplete(string $filename, string $sniffClass, File $report): void
{
}
public function notifyFileComplete(string $filename, File $report): void
{
}
public function notifyRunComplete(Report $report): void
{
$this->printer->writeReport($report);
}
}
<?php declare(strict_types=1);
namespace Helmich\TypoScriptLint\Logging;
use Helmich\TypoScriptLint\Linter\Report\File;
use Helmich\TypoScriptLint\Linter\Report\Report;
/**
* Special logger that does literally nothing
*
* Useful for unit testing.
*
* @author Martin Helmich <typo3@martin-helmich.de>
* @license MIT
* @package Helmich\TypoScriptLint
* @subpackage Logging
*/
class NullLogger implements LinterLoggerInterface
{
public function notifyFileNotFound(string $file): void
{
}
public function notifyFiles(array $files): void
{
}
public function notifyFileStart(string $filename): void
{
}
public function notifyFileSniffStart(string $filename, string $sniffClass): void
{
}
public function nofifyFileSniffComplete(string $filename, string $sniffClass, File $report): void
{
}
public function notifyFileComplete(string $filename, File $report): void
{
}
public function notifyRunComplete(Report $report): void
{
}
}<?php declare(strict_types=1);
namespace Helmich\TypoScriptLint\Logging;
use Helmich\TypoScriptLint\Linter\Report\File;
use Helmich\TypoScriptLint\Linter\Report\Issue;
use Helmich\TypoScriptLint\Linter\Report\Report;
use Helmich\TypoScriptLint\Linter\ReportPrinter\Printer;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Compact console logger
*
* This logger prints a compact progress report to the console, similar to PHPUnit.
*
* @author Martin Helmich <typo3@martin-helmich.de>
* @license MIT
* @package Helmich\TypoScriptLint
* @subpackage Logging
*/
class CompactConsoleLogger implements LinterLoggerInterface
{
const OUTPUT_WIDTH = 50;
/** @var int */
private $fileCount = 0;
/** @var int */
private $issueCount = 0;
/** @var int */
private $fileCompletedCount = 0;
/** @var OutputInterface */
private $output;
/** @var string */
private $progressFormatString = " [%3d / %-3d, %3d%%]";
/** @var Printer */
private $printer;
public function __construct(Printer $printer, OutputInterface $output)
{
$this->output = $output;
$this->printer = $printer;
}
public function notifyFileNotFound(string $file): void
{
$this->output->writeln("<error>WARNING: Input file ${file} does not seem to exist.</error>");
}
public function notifyFiles(array $files): void
{
$this->fileCount = count($files);
$numCount = strlen("" . $this->fileCount);
$this->progressFormatString = " [%{$numCount}d / %-{$numCount}d, %3d%%]";
}
public function notifyFileStart(string $filename): void
{
}
public function notifyFileSniffStart(string $filename, string $sniffClass): void
{
}
public function nofifyFileSniffComplete(string $filename, string $sniffClass, File $report): void
{
}
public function notifyFileComplete(string $filename, File $report): void
{
if (count($report->getIssuesBySeverity(Issue::SEVERITY_ERROR)) > 0) {
$this->output->write("<error>E</error>");
} elseif (count($report->getIssuesBySeverity(Issue::SEVERITY_WARNING)) > 0) {
$this->output->write("<comment>W</comment>");
} elseif (count($report->getIssuesBySeverity(Issue::SEVERITY_INFO)) > 0) {
$this->output->write("<comment>I</comment>");
} else {
$this->output->write("<info>.</info>");
}
$this->fileCompletedCount += 1;
$this->issueCount += count($report->getIssues());
if ($this->fileCompletedCount % self::OUTPUT_WIDTH === 0) {
$this->printProgress();
}
}
private function printProgress(): void
{
$this->output->writeln(sprintf($this->progressFormatString, $this->fileCompletedCount, $this->fileCount, $this->fileCompletedCount / $this->fileCount * 100));
}
public function notifyRunComplete(Report $report): void
{
$remaining = $this->fileCompletedCount % self::OUTPUT_WIDTH;
if ($remaining !== 0) {
$this->output->write(str_repeat(' ', self::OUTPUT_WIDTH - $remaining));
$this->printProgress();
}
$this->output->write("\n");
if ($this->issueCount > 0) {
$this->output->writeln("Completed with <comment>{$this->issueCount} issues</comment>");
$this->printer->writeReport($report);
} else {
$this->output->writeln("Complete <info>without warnings</info>");
}
}
}
<?php declare(strict_types=1);
namespace Helmich\TypoScriptLint\Logging;
use Helmich\TypoScriptLint\Linter\Report\File;
use Helmich\TypoScriptLint\Linter\Report\Report;
use Helmich\TypoScriptLint\Linter\ReportPrinter\Printer;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Verbose console logger
*
* This logger replaces the original, hard-coded default behaviour.
*
* Each and every one sniff is printed for each file. This behaviour was
* mothballed since it was WAY to verbose for large projects and will likely
* be entirely removed in later releases.
*
* @author Martin Helmich <typo3@martin-helmich.de>
* @license MIT
* @package Helmich\TypoScriptLint
* @subpackage Logging
*/
class VerboseConsoleLogger implements LinterLoggerInterface
{
/** @var OutputInterface */
private $output;
/** @var Printer */
private $printer;
public function __construct(Printer $printer, OutputInterface $output)
{
$this->output = $output;
$this->printer = $printer;
}
public function notifyFileNotFound(string $file): void
{
$this->output->writeln("<error>WARNING: Input file ${file} does not seem to exist.</error>");
}
public function notifyFiles(array $files): void
{
}
public function notifyFileStart(string $filename): void
{
$this->output->writeln("Linting input file <comment>{$filename}</comment>.");
}
public function notifyFileSniffStart(string $filename, string $sniffClass): void
{
$this->output->writeln('=> <info>Executing sniff <comment>' . $sniffClass . '</comment>.</info>');
}
public function nofifyFileSniffComplete(string $filename, string $sniffClass, File $report): void
{
}
public function notifyFileComplete(string $filename, File $report): void
{
}
public function notifyRunComplete(Report $report): void
{
$this->printer->writeReport($report);
}
}<?php declare(strict_types=1);
namespace Helmich\TypoScriptLint;
use Exception;
use Helmich\TypoScriptLint\Command\LintCommand;
use Symfony\Component\Console\Application as SymfonyApplication;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\DependencyInjection\Container;
class Application extends SymfonyApplication
{
public const APP_NAME = 'typoscript-lint';
public const APP_VERSION = 'dev';
/** @var Container */
private $container;
public function __construct(Container $container)
{
$this->container = $container;
parent::__construct(static::APP_NAME, static::APP_VERSION);
}
protected function getCommandName(InputInterface $input)
{
return 'lint';
}
/**
* @return Command[]
* @throws Exception
*/
protected function getDefaultCommands()
{
/** @var LintCommand $lintCommand */
$lintCommand = $this->container->get("lint_command");
$defaultCommands = parent::getDefaultCommands();
$defaultCommands[] = $lintCommand;
return $defaultCommands;
}
public function getDefinition()
{
$inputDefinition = parent::getDefinition();
$inputDefinition->setArguments();
return $inputDefinition;
}
/**
* Gets the currently installed version number.
*
* In contrast to the overridden parent method, this variant is Composer-aware and
* will its own version from the first-best composer.lock file that it can find.
*
* @see https://github.com/martin-helmich/typo3-typoscript-lint/issues/35
* @return string
*/
public function getVersion()
{
$current = dirname(__FILE__);
while($current !== '/') {
if (file_exists($current . '/composer.lock')) {
$contents = file_get_contents($current . '/composer.lock');
if ($contents === false) {
continue;
}
$data = json_decode($contents);
$packages = array_values(array_filter($data->packages, function(\stdClass $package): bool {
return $package->name === "helmich/typo3-typoscript-lint";
}));
if (count($packages) > 0) {
return $packages[0]->version;
}
}
$current = dirname($current);
}
return parent::getVersion();
}
}
<?php
// autoload.php @generated by Composer
require_once __DIR__ . '/composer/autoload_real.php';
return ComposerAutoloaderInit12462603de42efb927e967434ba5a19a::getLoader();
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer\Autoload;
/**
* ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
*
* $loader = new \Composer\Autoload\ClassLoader();
*
* // register classes with namespaces
* $loader->add('Symfony\Component', __DIR__.'/component');
* $loader->add('Symfony', __DIR__.'/framework');
*
* // activate the autoloader
* $loader->register();
*
* // to enable searching the include path (eg. for PEAR packages)
* $loader->setUseIncludePath(true);
*
* In this example, if you try to use a class in the Symfony\Component
* namespace or one of its children (Symfony\Component\Console for instance),
* the autoloader will first look for the class under the component/
* directory, and it will then fallback to the framework/ directory if not
* found before giving up.
*
* This class is loosely based on the Symfony UniversalClassLoader.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Jordi Boggiano <j.boggiano@seld.be>
* @see https://www.php-fig.org/psr/psr-0/
* @see https://www.php-fig.org/psr/psr-4/
*/
class ClassLoader
{
// PSR-4
private $prefixLengthsPsr4 = array();
private $prefixDirsPsr4 = array();
private $fallbackDirsPsr4 = array();
// PSR-0
private $prefixesPsr0 = array();
private $fallbackDirsPsr0 = array();
private $useIncludePath = false;
private $classMap = array();
private $classMapAuthoritative = false;
private $missingClasses = array();
private $apcuPrefix;
public function getPrefixes()
{
if (!empty($this->prefixesPsr0)) {
return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
}
return array();
}
public function getPrefixesPsr4()
{
return $this->prefixDirsPsr4;
}
public function getFallbackDirs()
{
return $this->fallbackDirsPsr0;
}
public function getFallbackDirsPsr4()
{
return $this->fallbackDirsPsr4;
}
public function getClassMap()
{
return $this->classMap;
}
/**
* @param array $classMap Class to filename map
*/
public function addClassMap(array $classMap)
{
if ($this->classMap) {
$this->classMap = array_merge($this->classMap, $classMap);
} else {
$this->classMap = $classMap;
}
}
/**
* Registers a set of PSR-0 directories for a given prefix, either
* appending or prepending to the ones previously set for this prefix.
*
* @param string $prefix The prefix
* @param array|string $paths The PSR-0 root directories
* @param bool $prepend Whether to prepend the directories
*/
public function add($prefix, $paths, $prepend = false)
{
if (!$prefix) {
if ($prepend) {
$this->fallbackDirsPsr0 = array_merge(
(array) $paths,
$this->fallbackDirsPsr0
);
} else {
$this->fallbackDirsPsr0 = array_merge(
$this->fallbackDirsPsr0,
(array) $paths
);
}
return;
}
$first = $prefix[0];
if (!isset($this->prefixesPsr0[$first][$prefix])) {
$this->prefixesPsr0[$first][$prefix] = (array) $paths;
return;
}
if ($prepend) {
$this->prefixesPsr0[$first][$prefix] = array_merge(
(array) $paths,
$this->prefixesPsr0[$first][$prefix]
);
} else {
$this->prefixesPsr0[$first][$prefix] = array_merge(
$this->prefixesPsr0[$first][$prefix],
(array) $paths
);
}
}
/**
* Registers a set of PSR-4 directories for a given namespace, either
* appending or prepending to the ones previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param array|string $paths The PSR-4 base directories
* @param bool $prepend Whether to prepend the directories
*
* @throws \InvalidArgumentException
*/
public function addPsr4($prefix, $paths, $prepend = false)
{
if (!$prefix) {
// Register directories for the root namespace.
if ($prepend) {
$this->fallbackDirsPsr4 = array_merge(
(array) $paths,
$this->fallbackDirsPsr4
);
} else {
$this->fallbackDirsPsr4 = array_merge(
$this->fallbackDirsPsr4,
(array) $paths
);
}
} elseif (!isset($this->prefixDirsPsr4[$prefix])) {
// Register directories for a new namespace.
$length = strlen($prefix);
if ('\\' !== $prefix[$length - 1]) {
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
$this->prefixDirsPsr4[$prefix] = (array) $paths;
} elseif ($prepend) {
// Prepend directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
(array) $paths,
$this->prefixDirsPsr4[$prefix]
);
} else {
// Append directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
$this->prefixDirsPsr4[$prefix],
(array) $paths
);
}
}
/**
* Registers a set of PSR-0 directories for a given prefix,
* replacing any others previously set for this prefix.
*
* @param string $prefix The prefix
* @param array|string $paths The PSR-0 base directories
*/
public function set($prefix, $paths)
{
if (!$prefix) {
$this->fallbackDirsPsr0 = (array) $paths;
} else {
$this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
}
}
/**
* Registers a set of PSR-4 directories for a given namespace,
* replacing any others previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param array|string $paths The PSR-4 base directories
*
* @throws \InvalidArgumentException
*/
public function setPsr4($prefix, $paths)
{
if (!$prefix) {
$this->fallbackDirsPsr4 = (array) $paths;
} else {
$length = strlen($prefix);
if ('\\' !== $prefix[$length - 1]) {
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
$this->prefixDirsPsr4[$prefix] = (array) $paths;
}
}
/**
* Turns on searching the include path for class files.
*
* @param bool $useIncludePath
*/
public function setUseIncludePath($useIncludePath)
{
$this->useIncludePath = $useIncludePath;
}
/**
* Can be used to check if the autoloader uses the include path to check
* for classes.
*
* @return bool
*/
public function getUseIncludePath()
{
return $this->useIncludePath;
}
/**
* Turns off searching the prefix and fallback directories for classes
* that have not been registered with the class map.
*
* @param bool $classMapAuthoritative
*/
public function setClassMapAuthoritative($classMapAuthoritative)
{
$this->classMapAuthoritative = $classMapAuthoritative;
}
/**
* Should class lookup fail if not found in the current class map?
*
* @return bool
*/
public function isClassMapAuthoritative()
{
return $this->classMapAuthoritative;
}
/**
* APCu prefix to use to cache found/not-found classes, if the extension is enabled.
*
* @param string|null $apcuPrefix
*/
public function setApcuPrefix($apcuPrefix)
{
$this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
}
/**
* The APCu prefix in use, or null if APCu caching is not enabled.
*
* @return string|null
*/
public function getApcuPrefix()
{
return $this->apcuPrefix;
}
/**
* Registers this instance as an autoloader.
*
* @param bool $prepend Whether to prepend the autoloader or not
*/
public function register($prepend = false)
{
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
}
/**
* Unregisters this instance as an autoloader.
*/
public function unregister()
{
spl_autoload_unregister(array($this, 'loadClass'));
}
/**
* Loads the given class or interface.
*
* @param string $class The name of the class
* @return bool|null True if loaded, null otherwise
*/
public function loadClass($class)
{
if ($file = $this->findFile($class)) {
includeFile($file);
return true;
}
}
/**
* Finds the path to the file where the class is defined.
*
* @param string $class The name of the class
*
* @return string|false The path if found, false otherwise
*/
public function findFile($class)
{
// class map lookup
if (isset($this->classMap[$class])) {
return $this->classMap[$class];
}
if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
return false;
}
if (null !== $this->apcuPrefix) {
$file = apcu_fetch($this->apcuPrefix.$class, $hit);
if ($hit) {
return $file;
}
}
$file = $this->findFileWithExtension($class, '.php');
// Search for Hack files if we are running on HHVM
if (false === $file && defined('HHVM_VERSION')) {
$file = $this->findFileWithExtension($class, '.hh');
}
if (null !== $this->apcuPrefix) {
apcu_add($this->apcuPrefix.$class, $file);
}
if (false === $file) {
// Remember that this class does not exist.
$this->missingClasses[$class] = true;
}
return $file;
}
private function findFileWithExtension($class, $ext)
{
// PSR-4 lookup
$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
$first = $class[0];
if (isset($this->prefixLengthsPsr4[$first])) {
$subPath = $class;
while (false !== $lastPos = strrpos($subPath, '\\')) {
$subPath = substr($subPath, 0, $lastPos);
$search = $subPath . '\\';
if (isset($this->prefixDirsPsr4[$search])) {
$pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
foreach ($this->prefixDirsPsr4[$search] as $dir) {
if (file_exists($file = $dir . $pathEnd)) {
return $file;
}
}
}
}
}
// PSR-4 fallback dirs
foreach ($this->fallbackDirsPsr4 as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
return $file;
}
}
// PSR-0 lookup
if (false !== $pos = strrpos($class, '\\')) {
// namespaced class name
$logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
. strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
} else {
// PEAR-like class name
$logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
}
if (isset($this->prefixesPsr0[$first])) {
foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
if (0 === strpos($class, $prefix)) {
foreach ($dirs as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}
}
}
}
// PSR-0 fallback dirs
foreach ($this->fallbackDirsPsr0 as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}
// PSR-0 include paths.
if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
return $file;
}
return false;
}
}
/**
* Scope isolated include.
*
* Prevents access to $this/self from included files.
*/
function includeFile($file)
{
include $file;
}
<?php
namespace Composer;
use Composer\Semver\VersionParser;
class InstalledVersions
{
private static $installed = array (
'root' =>
array (
'pretty_version' => 'v2.4.1',
'version' => '2.4.1.0',
'aliases' =>
array (
),
'reference' => '9893afb62b7e9eca32258ee14d4f51069ba84c72',
'name' => 'helmich/typo3-typoscript-lint',
),
'versions' =>
array (
'helmich/typo3-typoscript-lint' =>
array (
'pretty_version' => 'v2.4.1',
'version' => '2.4.1.0',
'aliases' =>
array (
),
'reference' => '9893afb62b7e9eca32258ee14d4f51069ba84c72',
),
'helmich/typo3-typoscript-parser' =>
array (
'pretty_version' => 'v2.1.4',
'version' => '2.1.4.0',
'aliases' =>
array (
),
'reference' => 'eedd34773442722e114b9072afcb585110c5e8bf',
),
'psr/container' =>
array (
'pretty_version' => '1.0.0',
'version' => '1.0.0.0',
'aliases' =>
array (
),
'reference' => 'b7ce3b176482dbbc1245ebf52b181af44c2cf55f',
),
'psr/container-implementation' =>
array (
'provided' =>
array (
0 => '1.0',
),
),
'psr/event-dispatcher' =>
array (
'pretty_version' => '1.0.0',
'version' => '1.0.0.0',
'aliases' =>
array (
),
'reference' => 'dbefd12671e8a14ec7f180cab83036ed26714bb0',
),
'psr/event-dispatcher-implementation' =>
array (
'provided' =>
array (
0 => '1.0',
),
),
'psr/log-implementation' =>
array (
'provided' =>
array (
0 => '1.0',
),
),
'symfony/config' =>
array (
'pretty_version' => 'v5.2.0',
'version' => '5.2.0.0',
'aliases' =>
array (
),
'reference' => 'fa1219ecbf96bb5db59f2599cba0960a0d9c3aea',
),
'symfony/console' =>
array (
'pretty_version' => 'v5.2.0',
'version' => '5.2.0.0',
'aliases' =>
array (
),
'reference' => '3e0564fb08d44a98bd5f1960204c958e57bd586b',
),
'symfony/dependency-injection' =>
array (
'pretty_version' => 'v5.2.0',
'version' => '5.2.0.0',
'aliases' =>
array (
),
'reference' => '98cec9b9f410a4832e239949a41d47182862c3a4',
),
'symfony/deprecation-contracts' =>
array (
'pretty_version' => 'v2.2.0',
'version' => '2.2.0.0',
'aliases' =>
array (
),
'reference' => '5fa56b4074d1ae755beb55617ddafe6f5d78f665',
),
'symfony/event-dispatcher' =>
array (
'pretty_version' => 'v5.2.0',
'version' => '5.2.0.0',
'aliases' =>
array (
),
'reference' => 'aa13a09811e6d2ad43f8fb336bebdb7691d85d3c',
),
'symfony/event-dispatcher-contracts' =>
array (
'pretty_version' => 'v2.2.0',
'version' => '2.2.0.0',
'aliases' =>
array (
),
'reference' => '0ba7d54483095a198fa51781bc608d17e84dffa2',
),
'symfony/event-dispatcher-implementation' =>
array (
'provided' =>
array (
0 => '2.0',
),
),
'symfony/filesystem' =>
array (
'pretty_version' => 'v5.2.0',
'version' => '5.2.0.0',
'aliases' =>
array (
),
'reference' => 'bb92ba7f38b037e531908590a858a04d85c0e238',
),
'symfony/finder' =>
array (
'pretty_version' => 'v5.2.0',
'version' => '5.2.0.0',
'aliases' =>
array (
),
'reference' => 'fd8305521692f27eae3263895d1ef1571c71a78d',
),
'symfony/polyfill-ctype' =>
array (
'pretty_version' => 'v1.20.0',
'version' => '1.20.0.0',
'aliases' =>
array (
),
'reference' => 'f4ba089a5b6366e453971d3aad5fe8e897b37f41',
),
'symfony/polyfill-intl-grapheme' =>
array (
'pretty_version' => 'v1.20.0',
'version' => '1.20.0.0',
'aliases' =>
array (
),
'reference' => 'c7cf3f858ec7d70b89559d6e6eb1f7c2517d479c',
),
'symfony/polyfill-intl-normalizer' =>
array (
'pretty_version' => 'v1.20.0',
'version' => '1.20.0.0',
'aliases' =>
array (
),
'reference' => '727d1096295d807c309fb01a851577302394c897',
),
'symfony/polyfill-mbstring' =>
array (
'pretty_version' => 'v1.20.0',
'version' => '1.20.0.0',
'aliases' =>
array (
),
'reference' => '39d483bdf39be819deabf04ec872eb0b2410b531',
),
'symfony/polyfill-php73' =>
array (
'pretty_version' => 'v1.20.0',
'version' => '1.20.0.0',
'aliases' =>
array (
),
'reference' => '8ff431c517be11c78c48a39a66d37431e26a6bed',
),
'symfony/polyfill-php80' =>
array (
'pretty_version' => 'v1.20.0',
'version' => '1.20.0.0',
'aliases' =>
array (
),
'reference' => 'e70aa8b064c5b72d3df2abd5ab1e90464ad009de',
),
'symfony/service-contracts' =>
array (
'pretty_version' => 'v2.2.0',
'version' => '2.2.0.0',
'aliases' =>
array (
),
'reference' => 'd15da7ba4957ffb8f1747218be9e1a121fd298a1',
),
'symfony/service-implementation' =>
array (
'provided' =>
array (
0 => '1.0',
),
),
'symfony/string' =>
array (
'pretty_version' => 'v5.2.0',
'version' => '5.2.0.0',
'aliases' =>
array (
),
'reference' => '40e975edadd4e32cd16f3753b3bad65d9ac48242',
),
'symfony/yaml' =>
array (
'pretty_version' => 'v5.2.0',
'version' => '5.2.0.0',
'aliases' =>
array (
),
'reference' => 'bb73619b2ae5121bbbcd9f191dfd53ded17ae598',
),
),
);
public static function getInstalledPackages()
{
return array_keys(self::$installed['versions']);
}
public static function isInstalled($packageName)
{
return isset(self::$installed['versions'][$packageName]);
}
public static function satisfies(VersionParser $parser, $packageName, $constraint)
{
$constraint = $parser->parseConstraints($constraint);
$provided = $parser->parseConstraints(self::getVersionRanges($packageName));
return $provided->matches($constraint);
}
public static function getVersionRanges($packageName)
{
if (!isset(self::$installed['versions'][$packageName])) {
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
$ranges = array();
if (isset(self::$installed['versions'][$packageName]['pretty_version'])) {
$ranges[] = self::$installed['versions'][$packageName]['pretty_version'];
}
if (array_key_exists('aliases', self::$installed['versions'][$packageName])) {
$ranges = array_merge($ranges, self::$installed['versions'][$packageName]['aliases']);
}
if (array_key_exists('replaced', self::$installed['versions'][$packageName])) {
$ranges = array_merge($ranges, self::$installed['versions'][$packageName]['replaced']);
}
if (array_key_exists('provided', self::$installed['versions'][$packageName])) {
$ranges = array_merge($ranges, self::$installed['versions'][$packageName]['provided']);
}
return implode(' || ', $ranges);
}
public static function getVersion($packageName)
{
if (!isset(self::$installed['versions'][$packageName])) {
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
if (!isset(self::$installed['versions'][$packageName]['version'])) {
return null;
}
return self::$installed['versions'][$packageName]['version'];
}
public static function getPrettyVersion($packageName)
{
if (!isset(self::$installed['versions'][$packageName])) {
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
if (!isset(self::$installed['versions'][$packageName]['pretty_version'])) {
return null;
}
return self::$installed['versions'][$packageName]['pretty_version'];
}
public static function getReference($packageName)
{
if (!isset(self::$installed['versions'][$packageName])) {
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
if (!isset(self::$installed['versions'][$packageName]['reference'])) {
return null;
}
return self::$installed['versions'][$packageName]['reference'];
}
public static function getRootPackage()
{
return self::$installed['root'];
}
public static function getRawData()
{
return self::$installed;
}
public static function reload($data)
{
self::$installed = $data;
}
}
<?php
// autoload_classmap.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
'Attribute' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Attribute.php',
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
'JsonException' => $vendorDir . '/symfony/polyfill-php73/Resources/stubs/JsonException.php',
'Normalizer' => $vendorDir . '/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php',
'Stringable' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Stringable.php',
'UnhandledMatchError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php',
'ValueError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/ValueError.php',
);
<?php
// autoload_files.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
'6e3fae29631ef280660b3cdad06f25a8' => $vendorDir . '/symfony/deprecation-contracts/function.php',
'320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php',
'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php',
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php',
'e69f7f6ee287b969198c3c9d6777bd38' => $vendorDir . '/symfony/polyfill-intl-normalizer/bootstrap.php',
'8825ede83f2f289127722d4e842cf7e8' => $vendorDir . '/symfony/polyfill-intl-grapheme/bootstrap.php',
'b6b991a57620e2fb6b2f66f03fe9ddc2' => $vendorDir . '/symfony/string/Resources/functions.php',
'0d59ee240a4cd96ddbb4ff164fccea4d' => $vendorDir . '/symfony/polyfill-php73/bootstrap.php',
);
<?php
// autoload_namespaces.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
);
<?php
// autoload_psr4.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
'Symfony\\Polyfill\\Php80\\' => array($vendorDir . '/symfony/polyfill-php80'),
'Symfony\\Polyfill\\Php73\\' => array($vendorDir . '/symfony/polyfill-php73'),
'Symfony\\Polyfill\\Mbstring\\' => array($vendorDir . '/symfony/polyfill-mbstring'),
'Symfony\\Polyfill\\Intl\\Normalizer\\' => array($vendorDir . '/symfony/polyfill-intl-normalizer'),
'Symfony\\Polyfill\\Intl\\Grapheme\\' => array($vendorDir . '/symfony/polyfill-intl-grapheme'),
'Symfony\\Polyfill\\Ctype\\' => array($vendorDir . '/symfony/polyfill-ctype'),
'Symfony\\Contracts\\Service\\' => array($vendorDir . '/symfony/service-contracts'),
'Symfony\\Contracts\\EventDispatcher\\' => array($vendorDir . '/symfony/event-dispatcher-contracts'),
'Symfony\\Component\\Yaml\\' => array($vendorDir . '/symfony/yaml'),
'Symfony\\Component\\String\\' => array($vendorDir . '/symfony/string'),
'Symfony\\Component\\Finder\\' => array($vendorDir . '/symfony/finder'),
'Symfony\\Component\\Filesystem\\' => array($vendorDir . '/symfony/filesystem'),
'Symfony\\Component\\EventDispatcher\\' => array($vendorDir . '/symfony/event-dispatcher'),
'Symfony\\Component\\DependencyInjection\\' => array($vendorDir . '/symfony/dependency-injection'),
'Symfony\\Component\\Console\\' => array($vendorDir . '/symfony/console'),
'Symfony\\Component\\Config\\' => array($vendorDir . '/symfony/config'),
'Psr\\EventDispatcher\\' => array($vendorDir . '/psr/event-dispatcher/src'),
'Psr\\Container\\' => array($vendorDir . '/psr/container/src'),
'Helmich\\TypoScriptParser\\' => array($vendorDir . '/helmich/typo3-typoscript-parser/src'),
'Helmich\\TypoScriptLint\\' => array($baseDir . '/src'),
);
<?php
// autoload_real.php @generated by Composer
class ComposerAutoloaderInit12462603de42efb927e967434ba5a19a
{
private static $loader;
public static function loadClassLoader($class)
{
if ('Composer\Autoload\ClassLoader' === $class) {
require __DIR__ . '/ClassLoader.php';
}
}
/**
* @return \Composer\Autoload\ClassLoader
*/
public static function getLoader()
{
if (null !== self::$loader) {
return self::$loader;
}
require __DIR__ . '/platform_check.php';
spl_autoload_register(array('ComposerAutoloaderInit12462603de42efb927e967434ba5a19a', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader();
spl_autoload_unregister(array('ComposerAutoloaderInit12462603de42efb927e967434ba5a19a', 'loadClassLoader'));
$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
if ($useStaticLoader) {
require __DIR__ . '/autoload_static.php';
call_user_func(\Composer\Autoload\ComposerStaticInit12462603de42efb927e967434ba5a19a::getInitializer($loader));
} else {
$map = require __DIR__ . '/autoload_namespaces.php';
foreach ($map as $namespace => $path) {
$loader->set($namespace, $path);
}
$map = require __DIR__ . '/autoload_psr4.php';
foreach ($map as $namespace => $path) {
$loader->setPsr4($namespace, $path);
}
$classMap = require __DIR__ . '/autoload_classmap.php';
if ($classMap) {
$loader->addClassMap($classMap);
}
}
$loader->register(true);
if ($useStaticLoader) {
$includeFiles = Composer\Autoload\ComposerStaticInit12462603de42efb927e967434ba5a19a::$files;
} else {
$includeFiles = require __DIR__ . '/autoload_files.php';
}
foreach ($includeFiles as $fileIdentifier => $file) {
composerRequire12462603de42efb927e967434ba5a19a($fileIdentifier, $file);
}
return $loader;
}
}
function composerRequire12462603de42efb927e967434ba5a19a($fileIdentifier, $file)
{
if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
require $file;
$GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
}
}
<?php
// autoload_static.php @generated by Composer
namespace Composer\Autoload;
class ComposerStaticInit12462603de42efb927e967434ba5a19a
{
public static $files = array (
'6e3fae29631ef280660b3cdad06f25a8' => __DIR__ . '/..' . '/symfony/deprecation-contracts/function.php',
'320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php',
'a4a119a56e50fbb293281d9a48007e0e' => __DIR__ . '/..' . '/symfony/polyfill-php80/bootstrap.php',
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php',
'e69f7f6ee287b969198c3c9d6777bd38' => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer/bootstrap.php',
'8825ede83f2f289127722d4e842cf7e8' => __DIR__ . '/..' . '/symfony/polyfill-intl-grapheme/bootstrap.php',
'b6b991a57620e2fb6b2f66f03fe9ddc2' => __DIR__ . '/..' . '/symfony/string/Resources/functions.php',
'0d59ee240a4cd96ddbb4ff164fccea4d' => __DIR__ . '/..' . '/symfony/polyfill-php73/bootstrap.php',
);
public static $prefixLengthsPsr4 = array (
'S' =>
array (
'Symfony\\Polyfill\\Php80\\' => 23,
'Symfony\\Polyfill\\Php73\\' => 23,
'Symfony\\Polyfill\\Mbstring\\' => 26,
'Symfony\\Polyfill\\Intl\\Normalizer\\' => 33,
'Symfony\\Polyfill\\Intl\\Grapheme\\' => 31,
'Symfony\\Polyfill\\Ctype\\' => 23,
'Symfony\\Contracts\\Service\\' => 26,
'Symfony\\Contracts\\EventDispatcher\\' => 34,
'Symfony\\Component\\Yaml\\' => 23,
'Symfony\\Component\\String\\' => 25,
'Symfony\\Component\\Finder\\' => 25,
'Symfony\\Component\\Filesystem\\' => 29,
'Symfony\\Component\\EventDispatcher\\' => 34,
'Symfony\\Component\\DependencyInjection\\' => 38,
'Symfony\\Component\\Console\\' => 26,
'Symfony\\Component\\Config\\' => 25,
),
'P' =>
array (
'Psr\\EventDispatcher\\' => 20,
'Psr\\Container\\' => 14,
),
'H' =>
array (
'Helmich\\TypoScriptParser\\' => 25,
'Helmich\\TypoScriptLint\\' => 23,
),
);
public static $prefixDirsPsr4 = array (
'Symfony\\Polyfill\\Php80\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/polyfill-php80',
),
'Symfony\\Polyfill\\Php73\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/polyfill-php73',
),
'Symfony\\Polyfill\\Mbstring\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring',
),
'Symfony\\Polyfill\\Intl\\Normalizer\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer',
),
'Symfony\\Polyfill\\Intl\\Grapheme\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/polyfill-intl-grapheme',
),
'Symfony\\Polyfill\\Ctype\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/polyfill-ctype',
),
'Symfony\\Contracts\\Service\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/service-contracts',
),
'Symfony\\Contracts\\EventDispatcher\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/event-dispatcher-contracts',
),
'Symfony\\Component\\Yaml\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/yaml',
),
'Symfony\\Component\\String\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/string',
),
'Symfony\\Component\\Finder\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/finder',
),
'Symfony\\Component\\Filesystem\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/filesystem',
),
'Symfony\\Component\\EventDispatcher\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/event-dispatcher',
),
'Symfony\\Component\\DependencyInjection\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/dependency-injection',
),
'Symfony\\Component\\Console\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/console',
),
'Symfony\\Component\\Config\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/config',
),
'Psr\\EventDispatcher\\' =>
array (
0 => __DIR__ . '/..' . '/psr/event-dispatcher/src',
),
'Psr\\Container\\' =>
array (
0 => __DIR__ . '/..' . '/psr/container/src',
),
'Helmich\\TypoScriptParser\\' =>
array (
0 => __DIR__ . '/..' . '/helmich/typo3-typoscript-parser/src',
),
'Helmich\\TypoScriptLint\\' =>
array (
0 => __DIR__ . '/../..' . '/src',
),
);
public static $classMap = array (
'Attribute' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Attribute.php',
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
'JsonException' => __DIR__ . '/..' . '/symfony/polyfill-php73/Resources/stubs/JsonException.php',
'Normalizer' => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php',
'Stringable' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Stringable.php',
'UnhandledMatchError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php',
'ValueError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/ValueError.php',
);
public static function getInitializer(ClassLoader $loader)
{
return \Closure::bind(function () use ($loader) {
$loader->prefixLengthsPsr4 = ComposerStaticInit12462603de42efb927e967434ba5a19a::$prefixLengthsPsr4;
$loader->prefixDirsPsr4 = ComposerStaticInit12462603de42efb927e967434ba5a19a::$prefixDirsPsr4;
$loader->classMap = ComposerStaticInit12462603de42efb927e967434ba5a19a::$classMap;
}, null, ClassLoader::class);
}
}
{
"packages": [
{
"name": "helmich/typo3-typoscript-parser",
"version": "v2.1.4",
"version_normalized": "2.1.4.0",
"source": {
"type": "git",
"url": "https://github.com/martin-helmich/typo3-typoscript-parser.git",
"reference": "eedd34773442722e114b9072afcb585110c5e8bf"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/martin-helmich/typo3-typoscript-parser/zipball/eedd34773442722e114b9072afcb585110c5e8bf",
"reference": "eedd34773442722e114b9072afcb585110c5e8bf",
"shasum": ""
},
"require": {
"php": ">=7.2",
"symfony/config": "~3.0|~4.0|~5.0",
"symfony/dependency-injection": "~3.0|~4.0|~5.0",
"symfony/yaml": "~3.0|~4.0|~5.0"
},
"require-dev": {
"php-vfs/php-vfs": "^1.3",
"phpunit/phpunit": "^8.0",
"symfony/phpunit-bridge": "~2.7|~3.0|~4.0|~5.0",
"vimeo/psalm": "^3.7"
},
"time": "2020-01-02T13:07:43+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"Helmich\\TypoScriptParser\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Martin Helmich",
"email": "m.helmich@mittwald.de",
"role": "lead"
}
],
"description": "Parser for the TYPO3 configuration language TypoScript.",
"homepage": "https://github.com/martin-helmich",
"support": {
"issues": "https://github.com/martin-helmich/typo3-typoscript-parser/issues",
"source": "https://github.com/martin-helmich/typo3-typoscript-parser/tree/master"
},
"install-path": "../helmich/typo3-typoscript-parser"
},
{
"name": "psr/container",
"version": "1.0.0",
"version_normalized": "1.0.0.0",
"source": {
"type": "git",
"url": "https://github.com/php-fig/container.git",
"reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f",
"reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"time": "2017-02-14T16:28:37+00:00",
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"installation-source": "dist",
"autoload": {
"psr-4": {
"Psr\\Container\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "http://www.php-fig.org/"
}
],
"description": "Common Container Interface (PHP FIG PSR-11)",
"homepage": "https://github.com/php-fig/container",
"keywords": [
"PSR-11",
"container",
"container-interface",
"container-interop",
"psr"
],
"support": {
"issues": "https://github.com/php-fig/container/issues",
"source": "https://github.com/php-fig/container/tree/master"
},
"install-path": "../psr/container"
},
{
"name": "psr/event-dispatcher",
"version": "1.0.0",
"version_normalized": "1.0.0.0",
"source": {
"type": "git",
"url": "https://github.com/php-fig/event-dispatcher.git",
"reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0",
"reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0",
"shasum": ""
},
"require": {
"php": ">=7.2.0"
},
"time": "2019-01-08T18:20:26+00:00",
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"installation-source": "dist",
"autoload": {
"psr-4": {
"Psr\\EventDispatcher\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "http://www.php-fig.org/"
}
],
"description": "Standard interfaces for event handling.",
"keywords": [
"events",
"psr",
"psr-14"
],
"support": {
"issues": "https://github.com/php-fig/event-dispatcher/issues",
"source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0"
},
"install-path": "../psr/event-dispatcher"
},
{
"name": "symfony/config",
"version": "v5.2.0",
"version_normalized": "5.2.0.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/config.git",
"reference": "fa1219ecbf96bb5db59f2599cba0960a0d9c3aea"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/config/zipball/fa1219ecbf96bb5db59f2599cba0960a0d9c3aea",
"reference": "fa1219ecbf96bb5db59f2599cba0960a0d9c3aea",
"shasum": ""
},
"require": {
"php": ">=7.2.5",
"symfony/deprecation-contracts": "^2.1",
"symfony/filesystem": "^4.4|^5.0",
"symfony/polyfill-ctype": "~1.8",
"symfony/polyfill-php80": "^1.15"
},
"conflict": {
"symfony/finder": "<4.4"
},
"require-dev": {
"symfony/event-dispatcher": "^4.4|^5.0",
"symfony/finder": "^4.4|^5.0",
"symfony/messenger": "^4.4|^5.0",
"symfony/service-contracts": "^1.1|^2",
"symfony/yaml": "^4.4|^5.0"
},
"suggest": {
"symfony/yaml": "To use the yaml reference dumper"
},
"time": "2020-11-16T18:02:40+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"Symfony\\Component\\Config\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony Config Component",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/config/tree/v5.2.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"install-path": "../symfony/config"
},
{
"name": "symfony/console",
"version": "v5.2.0",
"version_normalized": "5.2.0.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
"reference": "3e0564fb08d44a98bd5f1960204c958e57bd586b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/3e0564fb08d44a98bd5f1960204c958e57bd586b",
"reference": "3e0564fb08d44a98bd5f1960204c958e57bd586b",
"shasum": ""
},
"require": {
"php": ">=7.2.5",
"symfony/polyfill-mbstring": "~1.0",
"symfony/polyfill-php73": "^1.8",
"symfony/polyfill-php80": "^1.15",
"symfony/service-contracts": "^1.1|^2",
"symfony/string": "^5.1"
},
"conflict": {
"symfony/dependency-injection": "<4.4",
"symfony/dotenv": "<5.1",
"symfony/event-dispatcher": "<4.4",
"symfony/lock": "<4.4",
"symfony/process": "<4.4"
},
"provide": {
"psr/log-implementation": "1.0"
},
"require-dev": {
"psr/log": "~1.0",
"symfony/config": "^4.4|^5.0",
"symfony/dependency-injection": "^4.4|^5.0",
"symfony/event-dispatcher": "^4.4|^5.0",
"symfony/lock": "^4.4|^5.0",
"symfony/process": "^4.4|^5.0",
"symfony/var-dumper": "^4.4|^5.0"
},
"suggest": {
"psr/log": "For using the console logger",
"symfony/event-dispatcher": "",
"symfony/lock": "",
"symfony/process": ""
},
"time": "2020-11-28T11:24:18+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"Symfony\\Component\\Console\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony Console Component",
"homepage": "https://symfony.com",
"keywords": [
"cli",
"command line",
"console",
"terminal"
],
"support": {
"source": "https://github.com/symfony/console/tree/v5.2.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"install-path": "../symfony/console"
},
{
"name": "symfony/dependency-injection",
"version": "v5.2.0",
"version_normalized": "5.2.0.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/dependency-injection.git",
"reference": "98cec9b9f410a4832e239949a41d47182862c3a4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/dependency-injection/zipball/98cec9b9f410a4832e239949a41d47182862c3a4",
"reference": "98cec9b9f410a4832e239949a41d47182862c3a4",
"shasum": ""
},
"require": {
"php": ">=7.2.5",
"psr/container": "^1.0",
"symfony/deprecation-contracts": "^2.1",
"symfony/polyfill-php80": "^1.15",
"symfony/service-contracts": "^1.1.6|^2"
},
"conflict": {
"symfony/config": "<5.1",
"symfony/finder": "<4.4",
"symfony/proxy-manager-bridge": "<4.4",
"symfony/yaml": "<4.4"
},
"provide": {
"psr/container-implementation": "1.0",
"symfony/service-implementation": "1.0"
},
"require-dev": {
"symfony/config": "^5.1",
"symfony/expression-language": "^4.4|^5.0",
"symfony/yaml": "^4.4|^5.0"
},
"suggest": {
"symfony/config": "",
"symfony/expression-language": "For using expressions in service container configuration",
"symfony/finder": "For using double-star glob patterns or when GLOB_BRACE portability is required",
"symfony/proxy-manager-bridge": "Generate service proxies to lazy load them",
"symfony/yaml": ""
},
"time": "2020-11-28T11:24:18+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"Symfony\\Component\\DependencyInjection\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony DependencyInjection Component",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/dependency-injection/tree/v5.2.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"install-path": "../symfony/dependency-injection"
},
{
"name": "symfony/deprecation-contracts",
"version": "v2.2.0",
"version_normalized": "2.2.0.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/deprecation-contracts.git",
"reference": "5fa56b4074d1ae755beb55617ddafe6f5d78f665"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/5fa56b4074d1ae755beb55617ddafe6f5d78f665",
"reference": "5fa56b4074d1ae755beb55617ddafe6f5d78f665",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"time": "2020-09-07T11:33:47+00:00",
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.2-dev"
},
"thanks": {
"name": "symfony/contracts",
"url": "https://github.com/symfony/contracts"
}
},
"installation-source": "dist",
"autoload": {
"files": [
"function.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "A generic function and convention to trigger deprecation notices",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/deprecation-contracts/tree/master"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"install-path": "../symfony/deprecation-contracts"
},
{
"name": "symfony/event-dispatcher",
"version": "v5.2.0",
"version_normalized": "5.2.0.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/event-dispatcher.git",
"reference": "aa13a09811e6d2ad43f8fb336bebdb7691d85d3c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/aa13a09811e6d2ad43f8fb336bebdb7691d85d3c",
"reference": "aa13a09811e6d2ad43f8fb336bebdb7691d85d3c",
"shasum": ""
},
"require": {
"php": ">=7.2.5",
"symfony/deprecation-contracts": "^2.1",
"symfony/event-dispatcher-contracts": "^2",
"symfony/polyfill-php80": "^1.15"
},
"conflict": {
"symfony/dependency-injection": "<4.4"
},
"provide": {
"psr/event-dispatcher-implementation": "1.0",
"symfony/event-dispatcher-implementation": "2.0"
},
"require-dev": {
"psr/log": "~1.0",
"symfony/config": "^4.4|^5.0",
"symfony/dependency-injection": "^4.4|^5.0",
"symfony/error-handler": "^4.4|^5.0",
"symfony/expression-language": "^4.4|^5.0",
"symfony/http-foundation": "^4.4|^5.0",
"symfony/service-contracts": "^1.1|^2",
"symfony/stopwatch": "^4.4|^5.0"
},
"suggest": {
"symfony/dependency-injection": "",
"symfony/http-kernel": ""
},
"time": "2020-11-01T16:14:45+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"Symfony\\Component\\EventDispatcher\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony EventDispatcher Component",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/event-dispatcher/tree/v5.2.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"install-path": "../symfony/event-dispatcher"
},
{
"name": "symfony/event-dispatcher-contracts",
"version": "v2.2.0",
"version_normalized": "2.2.0.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/event-dispatcher-contracts.git",
"reference": "0ba7d54483095a198fa51781bc608d17e84dffa2"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/0ba7d54483095a198fa51781bc608d17e84dffa2",
"reference": "0ba7d54483095a198fa51781bc608d17e84dffa2",
"shasum": ""
},
"require": {
"php": ">=7.2.5",
"psr/event-dispatcher": "^1"
},
"suggest": {
"symfony/event-dispatcher-implementation": ""
},
"time": "2020-09-07T11:33:47+00:00",
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.2-dev"
},
"thanks": {
"name": "symfony/contracts",
"url": "https://github.com/symfony/contracts"
}
},
"installation-source": "dist",
"autoload": {
"psr-4": {
"Symfony\\Contracts\\EventDispatcher\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Generic abstractions related to dispatching event",
"homepage": "https://symfony.com",
"keywords": [
"abstractions",
"contracts",
"decoupling",
"interfaces",
"interoperability",
"standards"
],
"support": {
"source": "https://github.com/symfony/event-dispatcher-contracts/tree/v2.2.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"install-path": "../symfony/event-dispatcher-contracts"
},
{
"name": "symfony/filesystem",
"version": "v5.2.0",
"version_normalized": "5.2.0.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/filesystem.git",
"reference": "bb92ba7f38b037e531908590a858a04d85c0e238"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/filesystem/zipball/bb92ba7f38b037e531908590a858a04d85c0e238",
"reference": "bb92ba7f38b037e531908590a858a04d85c0e238",
"shasum": ""
},
"require": {
"php": ">=7.2.5",
"symfony/polyfill-ctype": "~1.8"
},
"time": "2020-11-12T09:58:18+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"Symfony\\Component\\Filesystem\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony Filesystem Component",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/filesystem/tree/v5.2.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"install-path": "../symfony/filesystem"
},
{
"name": "symfony/finder",
"version": "v5.2.0",
"version_normalized": "5.2.0.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/finder.git",
"reference": "fd8305521692f27eae3263895d1ef1571c71a78d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/finder/zipball/fd8305521692f27eae3263895d1ef1571c71a78d",
"reference": "fd8305521692f27eae3263895d1ef1571c71a78d",
"shasum": ""
},
"require": {
"php": ">=7.2.5"
},
"time": "2020-11-18T09:42:36+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"Symfony\\Component\\Finder\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony Finder Component",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/finder/tree/v5.2.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"install-path": "../symfony/finder"
},
{
"name": "symfony/polyfill-ctype",
"version": "v1.20.0",
"version_normalized": "1.20.0.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-ctype.git",
"reference": "f4ba089a5b6366e453971d3aad5fe8e897b37f41"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/f4ba089a5b6366e453971d3aad5fe8e897b37f41",
"reference": "f4ba089a5b6366e453971d3aad5fe8e897b37f41",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"suggest": {
"ext-ctype": "For best performance"
},
"time": "2020-10-23T14:02:19+00:00",
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.20-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"installation-source": "dist",
"autoload": {
"psr-4": {
"Symfony\\Polyfill\\Ctype\\": ""
},
"files": [
"bootstrap.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Gert de Pagter",
"email": "BackEndTea@gmail.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for ctype functions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"ctype",
"polyfill",
"portable"
],
"support": {
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.20.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"install-path": "../symfony/polyfill-ctype"
},
{
"name": "symfony/polyfill-intl-grapheme",
"version": "v1.20.0",
"version_normalized": "1.20.0.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-intl-grapheme.git",
"reference": "c7cf3f858ec7d70b89559d6e6eb1f7c2517d479c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/c7cf3f858ec7d70b89559d6e6eb1f7c2517d479c",
"reference": "c7cf3f858ec7d70b89559d6e6eb1f7c2517d479c",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"suggest": {
"ext-intl": "For best performance"
},
"time": "2020-10-23T14:02:19+00:00",
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.20-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"installation-source": "dist",
"autoload": {
"psr-4": {
"Symfony\\Polyfill\\Intl\\Grapheme\\": ""
},
"files": [
"bootstrap.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for intl's grapheme_* functions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"grapheme",
"intl",
"polyfill",
"portable",
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.20.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"install-path": "../symfony/polyfill-intl-grapheme"
},
{
"name": "symfony/polyfill-intl-normalizer",
"version": "v1.20.0",
"version_normalized": "1.20.0.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-intl-normalizer.git",
"reference": "727d1096295d807c309fb01a851577302394c897"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/727d1096295d807c309fb01a851577302394c897",
"reference": "727d1096295d807c309fb01a851577302394c897",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"suggest": {
"ext-intl": "For best performance"
},
"time": "2020-10-23T14:02:19+00:00",
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.20-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"installation-source": "dist",
"autoload": {
"psr-4": {
"Symfony\\Polyfill\\Intl\\Normalizer\\": ""
},
"files": [
"bootstrap.php"
],
"classmap": [
"Resources/stubs"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for intl's Normalizer class and related functions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"intl",
"normalizer",
"polyfill",
"portable",
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.20.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"install-path": "../symfony/polyfill-intl-normalizer"
},
{
"name": "symfony/polyfill-mbstring",
"version": "v1.20.0",
"version_normalized": "1.20.0.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
"reference": "39d483bdf39be819deabf04ec872eb0b2410b531"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/39d483bdf39be819deabf04ec872eb0b2410b531",
"reference": "39d483bdf39be819deabf04ec872eb0b2410b531",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"suggest": {
"ext-mbstring": "For best performance"
},
"time": "2020-10-23T14:02:19+00:00",
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.20-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"installation-source": "dist",
"autoload": {
"psr-4": {
"Symfony\\Polyfill\\Mbstring\\": ""
},
"files": [
"bootstrap.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for the Mbstring extension",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"mbstring",
"polyfill",
"portable",
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.20.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"install-path": "../symfony/polyfill-mbstring"
},
{
"name": "symfony/polyfill-php73",
"version": "v1.20.0",
"version_normalized": "1.20.0.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php73.git",
"reference": "8ff431c517be11c78c48a39a66d37431e26a6bed"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/8ff431c517be11c78c48a39a66d37431e26a6bed",
"reference": "8ff431c517be11c78c48a39a66d37431e26a6bed",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"time": "2020-10-23T14:02:19+00:00",
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.20-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"installation-source": "dist",
"autoload": {
"psr-4": {
"Symfony\\Polyfill\\Php73\\": ""
},
"files": [
"bootstrap.php"
],
"classmap": [
"Resources/stubs"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"polyfill",
"portable",
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-php73/tree/v1.20.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"install-path": "../symfony/polyfill-php73"
},
{
"name": "symfony/polyfill-php80",
"version": "v1.20.0",
"version_normalized": "1.20.0.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php80.git",
"reference": "e70aa8b064c5b72d3df2abd5ab1e90464ad009de"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/e70aa8b064c5b72d3df2abd5ab1e90464ad009de",
"reference": "e70aa8b064c5b72d3df2abd5ab1e90464ad009de",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"time": "2020-10-23T14:02:19+00:00",
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.20-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"installation-source": "dist",
"autoload": {
"psr-4": {
"Symfony\\Polyfill\\Php80\\": ""
},
"files": [
"bootstrap.php"
],
"classmap": [
"Resources/stubs"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Ion Bazan",
"email": "ion.bazan@gmail.com"
},
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"polyfill",
"portable",
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-php80/tree/v1.20.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"install-path": "../symfony/polyfill-php80"
},
{
"name": "symfony/service-contracts",
"version": "v2.2.0",
"version_normalized": "2.2.0.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/service-contracts.git",
"reference": "d15da7ba4957ffb8f1747218be9e1a121fd298a1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/service-contracts/zipball/d15da7ba4957ffb8f1747218be9e1a121fd298a1",
"reference": "d15da7ba4957ffb8f1747218be9e1a121fd298a1",
"shasum": ""
},
"require": {
"php": ">=7.2.5",
"psr/container": "^1.0"
},
"suggest": {
"symfony/service-implementation": ""
},
"time": "2020-09-07T11:33:47+00:00",
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.2-dev"
},
"thanks": {
"name": "symfony/contracts",
"url": "https://github.com/symfony/contracts"
}
},
"installation-source": "dist",
"autoload": {
"psr-4": {
"Symfony\\Contracts\\Service\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Generic abstractions related to writing services",
"homepage": "https://symfony.com",
"keywords": [
"abstractions",
"contracts",
"decoupling",
"interfaces",
"interoperability",
"standards"
],
"support": {
"source": "https://github.com/symfony/service-contracts/tree/master"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"install-path": "../symfony/service-contracts"
},
{
"name": "symfony/string",
"version": "v5.2.0",
"version_normalized": "5.2.0.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/string.git",
"reference": "40e975edadd4e32cd16f3753b3bad65d9ac48242"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/string/zipball/40e975edadd4e32cd16f3753b3bad65d9ac48242",
"reference": "40e975edadd4e32cd16f3753b3bad65d9ac48242",
"shasum": ""
},
"require": {
"php": ">=7.2.5",
"symfony/polyfill-ctype": "~1.8",
"symfony/polyfill-intl-grapheme": "~1.0",
"symfony/polyfill-intl-normalizer": "~1.0",
"symfony/polyfill-mbstring": "~1.0",
"symfony/polyfill-php80": "~1.15"
},
"require-dev": {
"symfony/error-handler": "^4.4|^5.0",
"symfony/http-client": "^4.4|^5.0",
"symfony/translation-contracts": "^1.1|^2",
"symfony/var-exporter": "^4.4|^5.0"
},
"time": "2020-10-24T12:08:07+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"Symfony\\Component\\String\\": ""
},
"files": [
"Resources/functions.php"
],
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony String component",
"homepage": "https://symfony.com",
"keywords": [
"grapheme",
"i18n",
"string",
"unicode",
"utf-8",
"utf8"
],
"support": {
"source": "https://github.com/symfony/string/tree/v5.2.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"install-path": "../symfony/string"
},
{
"name": "symfony/yaml",
"version": "v5.2.0",
"version_normalized": "5.2.0.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/yaml.git",
"reference": "bb73619b2ae5121bbbcd9f191dfd53ded17ae598"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/yaml/zipball/bb73619b2ae5121bbbcd9f191dfd53ded17ae598",
"reference": "bb73619b2ae5121bbbcd9f191dfd53ded17ae598",
"shasum": ""
},
"require": {
"php": ">=7.2.5",
"symfony/deprecation-contracts": "^2.1",
"symfony/polyfill-ctype": "~1.8"
},
"conflict": {
"symfony/console": "<4.4"
},
"require-dev": {
"symfony/console": "^4.4|^5.0"
},
"suggest": {
"symfony/console": "For validating YAML files using the lint command"
},
"time": "2020-11-28T10:57:20+00:00",
"bin": [
"Resources/bin/yaml-lint"
],
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"Symfony\\Component\\Yaml\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony Yaml Component",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/yaml/tree/v5.2.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"install-path": "../symfony/yaml"
}
],
"dev": false,
"dev-package-names": []
}
<?php return array (
'root' =>
array (
'pretty_version' => 'v2.4.1',
'version' => '2.4.1.0',
'aliases' =>
array (
),
'reference' => '9893afb62b7e9eca32258ee14d4f51069ba84c72',
'name' => 'helmich/typo3-typoscript-lint',
),
'versions' =>
array (
'helmich/typo3-typoscript-lint' =>
array (
'pretty_version' => 'v2.4.1',
'version' => '2.4.1.0',
'aliases' =>
array (
),
'reference' => '9893afb62b7e9eca32258ee14d4f51069ba84c72',
),
'helmich/typo3-typoscript-parser' =>
array (
'pretty_version' => 'v2.1.4',
'version' => '2.1.4.0',
'aliases' =>
array (
),
'reference' => 'eedd34773442722e114b9072afcb585110c5e8bf',
),
'psr/container' =>
array (
'pretty_version' => '1.0.0',
'version' => '1.0.0.0',
'aliases' =>
array (
),
'reference' => 'b7ce3b176482dbbc1245ebf52b181af44c2cf55f',
),
'psr/container-implementation' =>
array (
'provided' =>
array (
0 => '1.0',
),
),
'psr/event-dispatcher' =>
array (
'pretty_version' => '1.0.0',
'version' => '1.0.0.0',
'aliases' =>
array (
),
'reference' => 'dbefd12671e8a14ec7f180cab83036ed26714bb0',
),
'psr/event-dispatcher-implementation' =>
array (
'provided' =>
array (
0 => '1.0',
),
),
'psr/log-implementation' =>
array (
'provided' =>
array (
0 => '1.0',
),
),
'symfony/config' =>
array (
'pretty_version' => 'v5.2.0',
'version' => '5.2.0.0',
'aliases' =>
array (
),
'reference' => 'fa1219ecbf96bb5db59f2599cba0960a0d9c3aea',
),
'symfony/console' =>
array (
'pretty_version' => 'v5.2.0',
'version' => '5.2.0.0',
'aliases' =>
array (
),
'reference' => '3e0564fb08d44a98bd5f1960204c958e57bd586b',
),
'symfony/dependency-injection' =>
array (
'pretty_version' => 'v5.2.0',
'version' => '5.2.0.0',
'aliases' =>
array (
),
'reference' => '98cec9b9f410a4832e239949a41d47182862c3a4',
),
'symfony/deprecation-contracts' =>
array (
'pretty_version' => 'v2.2.0',
'version' => '2.2.0.0',
'aliases' =>
array (
),
'reference' => '5fa56b4074d1ae755beb55617ddafe6f5d78f665',
),
'symfony/event-dispatcher' =>
array (
'pretty_version' => 'v5.2.0',
'version' => '5.2.0.0',
'aliases' =>
array (
),
'reference' => 'aa13a09811e6d2ad43f8fb336bebdb7691d85d3c',
),
'symfony/event-dispatcher-contracts' =>
array (
'pretty_version' => 'v2.2.0',
'version' => '2.2.0.0',
'aliases' =>
array (
),
'reference' => '0ba7d54483095a198fa51781bc608d17e84dffa2',
),
'symfony/event-dispatcher-implementation' =>
array (
'provided' =>
array (
0 => '2.0',
),
),
'symfony/filesystem' =>
array (
'pretty_version' => 'v5.2.0',
'version' => '5.2.0.0',
'aliases' =>
array (
),
'reference' => 'bb92ba7f38b037e531908590a858a04d85c0e238',
),
'symfony/finder' =>
array (
'pretty_version' => 'v5.2.0',
'version' => '5.2.0.0',
'aliases' =>
array (
),
'reference' => 'fd8305521692f27eae3263895d1ef1571c71a78d',
),
'symfony/polyfill-ctype' =>
array (
'pretty_version' => 'v1.20.0',
'version' => '1.20.0.0',
'aliases' =>
array (
),
'reference' => 'f4ba089a5b6366e453971d3aad5fe8e897b37f41',
),
'symfony/polyfill-intl-grapheme' =>
array (
'pretty_version' => 'v1.20.0',
'version' => '1.20.0.0',
'aliases' =>
array (
),
'reference' => 'c7cf3f858ec7d70b89559d6e6eb1f7c2517d479c',
),
'symfony/polyfill-intl-normalizer' =>
array (
'pretty_version' => 'v1.20.0',
'version' => '1.20.0.0',
'aliases' =>
array (
),
'reference' => '727d1096295d807c309fb01a851577302394c897',
),
'symfony/polyfill-mbstring' =>
array (
'pretty_version' => 'v1.20.0',
'version' => '1.20.0.0',
'aliases' =>
array (
),
'reference' => '39d483bdf39be819deabf04ec872eb0b2410b531',
),
'symfony/polyfill-php73' =>
array (
'pretty_version' => 'v1.20.0',
'version' => '1.20.0.0',
'aliases' =>
array (
),
'reference' => '8ff431c517be11c78c48a39a66d37431e26a6bed',
),
'symfony/polyfill-php80' =>
array (
'pretty_version' => 'v1.20.0',
'version' => '1.20.0.0',
'aliases' =>
array (
),
'reference' => 'e70aa8b064c5b72d3df2abd5ab1e90464ad009de',
),
'symfony/service-contracts' =>
array (
'pretty_version' => 'v2.2.0',
'version' => '2.2.0.0',
'aliases' =>
array (
),
'reference' => 'd15da7ba4957ffb8f1747218be9e1a121fd298a1',
),
'symfony/service-implementation' =>
array (
'provided' =>
array (
0 => '1.0',
),
),
'symfony/string' =>
array (
'pretty_version' => 'v5.2.0',
'version' => '5.2.0.0',
'aliases' =>
array (
),
'reference' => '40e975edadd4e32cd16f3753b3bad65d9ac48242',
),
'symfony/yaml' =>
array (
'pretty_version' => 'v5.2.0',
'version' => '5.2.0.0',
'aliases' =>
array (
),
'reference' => 'bb73619b2ae5121bbbcd9f191dfd53ded17ae598',
),
),
);
<?php
// platform_check.php @generated by Composer
$issues = array();
if (!(PHP_VERSION_ID >= 70205)) {
$issues[] = 'Your Composer dependencies require a PHP version ">= 7.2.5". You are running ' . PHP_VERSION . '.';
}
if ($issues) {
if (!headers_sent()) {
header('HTTP/1.1 500 Internal Server Error');
}
if (!ini_get('display_errors')) {
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL);
} elseif (!headers_sent()) {
echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL;
}
}
trigger_error(
'Composer detected issues in your platform: ' . implode(' ', $issues),
E_USER_ERROR
);
}
services:
tokenizer:
class: Helmich\TypoScriptParser\Tokenizer\Tokenizer
token_printer_structured:
class: Helmich\TypoScriptParser\Tokenizer\Printer\StructuredTokenPrinter
token_printer_code:
class: Helmich\TypoScriptParser\Tokenizer\Printer\CodeTokenPrinter
parser:
class: Helmich\TypoScriptParser\Parser\Parser
arguments: ['@tokenizer']
<?php declare(strict_types=1);
namespace Helmich\TypoScriptParser\Parser;
use BadMethodCallException;
use Helmich\TypoScriptParser\Tokenizer\Token;
use Helmich\TypoScriptParser\Tokenizer\TokenInterface;
use Iterator;
/**
* Helper class that represents a token stream
*
* @package Helmich\TypoScriptParser
* @subpackage Parser
*/
class TokenStream implements Iterator, \ArrayAccess
{
/** @var array */
private $tokens;
/** @var int */
private $index = 0;
public function __construct(array $tokens)
{
$this->tokens = $tokens;
}
/**
* @param int $lookAhead
* @return TokenInterface
*/
public function current(int $lookAhead = 0): TokenInterface
{
return $this[$this->index + $lookAhead];
}
/**
* @param int $increment
* @return void
*/
public function next(int $increment = 1): void
{
if ($this->index < count($this->tokens)) {
$this->index += $increment;
}
}
/**
* @return bool
*/
public function valid(): bool
{
return ($this->index) < count($this->tokens);
}
/**
* @return void
*/
public function rewind(): void
{
$this->index = 0;
}
/**
* @return int
*/
public function key(): int
{
return $this->index;
}
/**
* @param int $offset
* @return bool
*/
public function offsetExists($offset): bool
{
return $offset >= 0 && $offset < count($this->tokens);
}
/**
* @param int $offset
* @return TokenInterface
*/
public function offsetGet($offset): TokenInterface
{
return $this->tokens[$offset];
}
/**
* @param int $offset
* @param TokenInterface $value
* @return void
*/
public function offsetSet($offset, $value)
{
throw new BadMethodCallException('changing a token stream is not permitted');
}
/**
* @param int $offset
* @return void
*/
public function offsetUnset($offset)
{
throw new BadMethodCallException('changing a token stream is not permitted');
}
/**
* Normalizes the token stream.
*
* This method transforms the token stream in a normalized form. This
* includes:
*
* - trimming whitespaces (remove leading and trailing whitespaces, as
* those are irrelevant for the parser)
* - remove both one-line and multi-line comments (also irrelevant for the
* parser)
*
* @return TokenStream
*/
public function normalized(): TokenStream
{
$filteredTokens = [];
$ignoredTokens = [
TokenInterface::TYPE_COMMENT_MULTILINE,
TokenInterface::TYPE_COMMENT_ONELINE,
];
$maxLine = 0;
foreach ($this->tokens as $token) {
$maxLine = (int)max($token->getLine(), $maxLine);
// Trim unnecessary whitespace, but leave line breaks! These are important!
if ($token->getType() === TokenInterface::TYPE_WHITESPACE) {
$value = trim($token->getValue(), "\t ");
if (strlen($value) > 0) {
$filteredTokens[] = new Token(
TokenInterface::TYPE_WHITESPACE,
$value,
$token->getLine(),
$token->getColumn()
);
}
} elseif (!in_array($token->getType(), $ignoredTokens)) {
$filteredTokens[] = $token;
}
}
// Add two linebreak tokens; during parsing, we usually do not look more than two
// tokens ahead; this hack ensures that there will always be at least two more tokens
// present and we do not have to check whether these tokens exists.
$filteredTokens[] = new Token(TokenInterface::TYPE_WHITESPACE, "\n", $maxLine + 1, 1);
$filteredTokens[] = new Token(TokenInterface::TYPE_WHITESPACE, "\n", $maxLine + 2, 1);
return new TokenStream($filteredTokens);
}
}
<?php declare(strict_types=1);
namespace Helmich\TypoScriptParser\Parser\Printer;
use Symfony\Component\Console\Output\OutputInterface;
interface ASTPrinterInterface
{
/**
* @param \Helmich\TypoScriptParser\Parser\AST\Statement[] $statements
* @param \Symfony\Component\Console\Output\OutputInterface $output
* @return void
*/
public function printStatements(array $statements, OutputInterface $output): void;
}
<?php declare(strict_types=1);
namespace Helmich\TypoScriptParser\Parser\Printer;
use Helmich\TypoScriptParser\Parser\AST\ConditionalStatement;
use Helmich\TypoScriptParser\Parser\AST\DirectoryIncludeStatement;
use Helmich\TypoScriptParser\Parser\AST\FileIncludeStatement;
use Helmich\TypoScriptParser\Parser\AST\IncludeStatement;
use Helmich\TypoScriptParser\Parser\AST\NestedAssignment;
use Helmich\TypoScriptParser\Parser\AST\Operator\Assignment;
use Helmich\TypoScriptParser\Parser\AST\Operator\BinaryObjectOperator;
use Helmich\TypoScriptParser\Parser\AST\Operator\Copy;
use Helmich\TypoScriptParser\Parser\AST\Operator\Delete;
use Helmich\TypoScriptParser\Parser\AST\Operator\Modification;
use Helmich\TypoScriptParser\Parser\AST\Operator\Reference;
use Helmich\TypoScriptParser\Parser\AST\Statement;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Printer class that generates TypoScript code from an AST
*
* @package Helmich\TypoScriptParser
* @subpackage Parser\Printer
*/
class PrettyPrinter implements ASTPrinterInterface
{
/**
* @param Statement[] $statements
* @param OutputInterface $output
* @return void
*/
public function printStatements(array $statements, OutputInterface $output): void
{
$this->printStatementList($statements, $output, 0);
}
/**
* @param Statement[] $statements
* @param OutputInterface $output
* @param int $nesting
* @return void
*/
private function printStatementList(array $statements, OutputInterface $output, int $nesting = 0): void
{
$indent = $this->getIndent($nesting);
$count = count($statements);
for ($i = 0; $i < $count; $i++) {
$statement = $statements[$i];
if ($statement instanceof NestedAssignment) {
$this->printNestedAssignment($output, $nesting, $statement);
} elseif ($statement instanceof Assignment) {
$this->printAssignment($output, $statement, $indent);
} elseif ($statement instanceof BinaryObjectOperator) {
$this->printBinaryObjectOperator($output, $statement, $nesting);
} elseif ($statement instanceof Delete) {
$output->writeln($indent . $statement->object->relativeName . ' >');
} elseif ($statement instanceof Modification) {
$output->writeln(
sprintf(
"%s%s := %s(%s)",
$indent,
$statement->object->relativeName,
$statement->call->method,
$statement->call->arguments
)
);
} elseif ($statement instanceof ConditionalStatement) {
$next = $i + 1 < $count ? $statements[$i + 1] : null;
$previous = $i - 1 >= 0 ? $statements[$i - 1] : null;
$this->printConditionalStatement(
$output,
$nesting,
$statement,
$next instanceof ConditionalStatement,
$previous instanceof ConditionalStatement
);
} elseif ($statement instanceof IncludeStatement) {
$this->printIncludeStatement($output, $statement);
}
}
}
private function getIndent(int $nesting): string
{
return str_repeat(' ', $nesting);
}
private function printBinaryObjectOperator(OutputInterface $output, BinaryObjectOperator $operator, int $nesting): void
{
$targetObjectPath = $operator->target->relativeName;
if ($operator instanceof Copy) {
$output->writeln($this->getIndent($nesting) . $operator->object->relativeName . ' < ' . $targetObjectPath);
} elseif ($operator instanceof Reference) {
$output->writeln($this->getIndent($nesting) . $operator->object->relativeName . ' =< ' . $targetObjectPath);
}
}
private function printIncludeStatement(OutputInterface $output, IncludeStatement $statement): void
{
if ($statement instanceof FileIncludeStatement) {
$this->printFileIncludeStatement($output, $statement);
} elseif ($statement instanceof DirectoryIncludeStatement) {
$this->printDirectoryIncludeStatement($output, $statement);
}
}
private function printFileIncludeStatement(OutputInterface $output, FileIncludeStatement $statement): void
{
if ($statement->newSyntax) {
$output->writeln('@import \'' . $statement->filename . '\'');
} else {
$attributes = "";
if ($statement->condition) {
$attributes = ' condition="' . $statement->condition . '"';
}
$output->writeln('<INCLUDE_TYPOSCRIPT: source="FILE:' . $statement->filename . '"' . $attributes . '>');
}
}
private function printDirectoryIncludeStatement(OutputInterface $output, DirectoryIncludeStatement $statement): void
{
$attributes = "";
if ($statement->extensions) {
$attributes .= ' extensions="' . $statement->extensions . '"';
}
if ($statement->condition) {
$attributes .= ' condition="' . $statement->condition . '"';
}
$includeStmt = '<INCLUDE_TYPOSCRIPT: source="DIR:' . $statement->directory . '"' . $attributes . '>';
$output->writeln($includeStmt);
}
/**
* @param OutputInterface $output
* @param int $nesting
* @param NestedAssignment $statement
*/
private function printNestedAssignment(OutputInterface $output, $nesting, NestedAssignment $statement): void
{
$output->writeln($this->getIndent($nesting) . $statement->object->relativeName . ' {');
$this->printStatementList($statement->statements, $output, $nesting + 1);
$output->writeln($this->getIndent($nesting) . '}');
}
/**
* @param OutputInterface $output
* @param int $nesting
* @param ConditionalStatement $statement
* @param bool $hasNext
* @param bool $hasPrevious
*/
private function printConditionalStatement(OutputInterface $output, int $nesting, ConditionalStatement $statement, bool $hasNext = false, bool $hasPrevious = false): void
{
if (!$hasPrevious) {
$output->writeln('');
}
$output->writeln($statement->condition);
$this->printStatementList($statement->ifStatements, $output, $nesting);
if (count($statement->elseStatements) > 0) {
$output->writeln('[else]');
$this->printStatementList($statement->elseStatements, $output, $nesting);
}
if (!$hasNext) {
$output->writeln('[global]');
}
}
/**
* @param OutputInterface $output
* @param Assignment $statement
* @param string $indent
*/
private function printAssignment(OutputInterface $output, Assignment $statement, string $indent): void
{
if (strpos($statement->value->value, "\n") !== false) {
$output->writeln($indent . $statement->object->relativeName . ' (');
$output->writeln(rtrim($statement->value->value));
$output->writeln($indent . ')');
return;
}
$output->writeln($indent . $statement->object->relativeName . ' = ' . $statement->value->value);
}
}
<?php declare(strict_types=1);
namespace Helmich\TypoScriptParser\Parser;
use ArrayObject;
use Helmich\TypoScriptParser\Parser\AST\Builder;
use Helmich\TypoScriptParser\Parser\AST\Statement;
use Helmich\TypoScriptParser\Tokenizer\TokenInterface;
use Helmich\TypoScriptParser\Tokenizer\TokenizerInterface;
/**
* Class Parser
*
* @package Helmich\TypoScriptParser
* @subpackage Parser
*/
class Parser implements ParserInterface
{
/** @var TokenizerInterface */
private $tokenizer;
/** @var Builder */
private $builder;
/**
* Parser constructor.
*
* @param TokenizerInterface $tokenizer
* @param Builder|null $astBuilder
*/
public function __construct(TokenizerInterface $tokenizer, Builder $astBuilder = null)
{
$this->tokenizer = $tokenizer;
$this->builder = $astBuilder ?: new Builder();
}
/**
* Parses a stream resource.
*
* This can be any kind of stream supported by PHP (e.g. a filename or a URL).
*
* @param string $stream The stream resource.
* @return Statement[] The syntax tree.
*/
public function parseStream(string $stream): array
{
$content = file_get_contents($stream);
if ($content === false) {
throw new \InvalidArgumentException("could not open file '${stream}'");
}
return $this->parseString($content);
}
/**
* Parses a TypoScript string.
*
* @param string $content The string to parse.
* @return Statement[] The syntax tree.
*/
public function parseString(string $content): array
{
$tokens = $this->tokenizer->tokenizeString($content);
return $this->parseTokens($tokens);
}
/**
* Parses a token stream.
*
* @param TokenInterface[] $tokens The token stream to parse.
* @return Statement[] The syntax tree.
*/
public function parseTokens(array $tokens): array
{
$stream = (new TokenStream($tokens))->normalized();
$state = new ParserState($stream);
for (; $state->hasNext(); $state->next()) {
if ($state->token()->getType() === TokenInterface::TYPE_OBJECT_IDENTIFIER) {
$objectPath = $this->builder->path($state->token()->getValue(), $state->token()->getValue());
if ($state->token(1)->getType() === TokenInterface::TYPE_BRACE_OPEN) {
$state->next(2);
$this->parseNestedStatements($state->withContext($objectPath));
}
}
$this->parseToken($state);
}
return $state->statements()->getArrayCopy();
}
/**
* @param ParserState $state
* @return void
* @throws ParseError
*/
private function parseToken(ParserState $state): void
{
switch ($state->token()->getType()) {
case TokenInterface::TYPE_OBJECT_IDENTIFIER:
$objectPath = $state->context()->append($state->token()->getValue());
$this->parseValueOperation($state->withContext($objectPath));
break;
case TokenInterface::TYPE_CONDITION:
$this->parseCondition($state);
break;
case TokenInterface::TYPE_INCLUDE:
case TokenInterface::TYPE_INCLUDE_NEW:
$this->parseInclude($state);
break;
case TokenInterface::TYPE_WHITESPACE:
break;
case TokenInterface::TYPE_BRACE_CLOSE:
$this->triggerParseErrorIf(
$state->context()->depth() === 0,
sprintf(
'Unexpected token %s when not in nested assignment in line %d.',
$state->token()->getType(),
$state->token()->getLine()
),
1403011203,
$state->token()->getLine()
);
break;
default:
throw new ParseError(
sprintf('Unexpected token %s in line %d.', $state->token()->getType(), $state->token()->getLine()),
1403011202,
$state->token()->getLine()
);
}
}
private function triggerParseErrorIf(bool $condition, string $message, int $code, int $line): void
{
if ($condition) {
throw new ParseError(
$message,
$code,
$line
);
}
}
/**
* @param ParserState $state
* @param int|null $startLine
* @return void
* @throws ParseError
*/
private function parseNestedStatements(ParserState $state, ?int $startLine = null): void
{
$startLine = $startLine ?: $state->token()->getLine();
$statements = new ArrayObject();
$subContext = $state->withStatements($statements);
for (; $state->hasNext(); $state->next()) {
if ($state->token()->getType() === TokenInterface::TYPE_OBJECT_IDENTIFIER) {
$objectPath = $this->builder->path(
$state->context()->absoluteName . '.' . $state->token()->getValue(),
$state->token()->getValue()
);
if ($state->token(1)->getType() === TokenInterface::TYPE_BRACE_OPEN) {
$state->next(2);
$this->parseNestedStatements(
$state->withContext($objectPath)->withStatements($statements)
);
continue;
}
}
$this->parseToken($subContext);
if ($state->token()->getType() === TokenInterface::TYPE_BRACE_CLOSE) {
$state->statements()->append($this->builder->nested(
$state->context(),
$statements->getArrayCopy(),
$startLine
));
$state->next();
return;
}
}
throw new ParseError('Unterminated nested statement!');
}
/**
* @param ParserState $state
* @throws ParseError
*/
private function parseCondition(ParserState $state): void
{
if ($state->context()->depth() !== 0) {
throw new ParseError(
'Found condition statement inside nested assignment.',
1403011203,
$state->token()->getLine()
);
}
$ifStatements = new ArrayObject();
$elseStatements = new ArrayObject();
$condition = $state->token()->getValue();
$conditionLine = $state->token()->getLine();
$inElseBranch = false;
$subContext = $state->withStatements($ifStatements);
$state->next();
for (; $state->hasNext(); $state->next()) {
if ($state->token()->getType() === TokenInterface::TYPE_CONDITION_END) {
$state->statements()->append($this->builder->condition(
$condition,
$ifStatements->getArrayCopy(),
$elseStatements->getArrayCopy(),
$conditionLine
));
$state->next();
break;
} elseif ($state->token()->getType() === TokenInterface::TYPE_CONDITION_ELSE) {
$this->triggerParseErrorIf(
$inElseBranch,
sprintf('Duplicate else in conditional statement in line %d.', $state->token()->getLine()),
1403011203,
$state->token()->getLine()
);
$inElseBranch = true;
$subContext = $subContext->withStatements($elseStatements);
$state->next();
} elseif ($state->token()->getType() === TokenInterface::TYPE_CONDITION) {
$state->statements()->append(
$this->builder->condition(
$condition,
$ifStatements->getArrayCopy(),
$elseStatements->getArrayCopy(),
$conditionLine
)
);
$this->parseCondition($state);
break;
}
if ($state->token()->getType() === TokenInterface::TYPE_OBJECT_IDENTIFIER) {
$objectPath = $this->builder->path($state->token()->getValue(), $state->token()->getValue());
if ($state->token(1)->getType() === TokenInterface::TYPE_BRACE_OPEN) {
$state->next(2);
$this->parseNestedStatements(
$subContext->withContext($objectPath),
$subContext->token(-2)->getLine()
);
}
}
$this->parseToken($subContext);
}
}
/**
* @param ParserState $state
*/
private function parseInclude(ParserState $state): void
{
$token = $state->token();
$extensions = null;
$condition = null;
$filename = $token->getSubMatch('filename') ?? '';
$optional = $token->getSubMatch('optional');
if ($optional !== null) {
list($extensions, $condition) = $this->parseIncludeOptionals($optional, $token);
}
if ($token->getType() === TokenInterface::TYPE_INCLUDE_NEW || $token->getSubMatch('type') === 'FILE') {
$node = $this->builder->includeFile(
$filename,
$token->getType() === TokenInterface::TYPE_INCLUDE_NEW,
$condition,
$token->getLine()
);
} else {
$node = $this->builder->includeDirectory(
$filename,
$extensions,
$condition,
$token->getLine()
);
}
$state->statements()->append($node);
}
/**
* @param string $optional
* @param TokenInterface $token
* @return array
* @throws ParseError
*/
private function parseIncludeOptionals(string $optional, TokenInterface $token): array
{
if (!preg_match_all('/((?<key>[a-z]+)="(?<value>[^"]*)\s*)+"/', $optional, $matches)) {
return [null, null];
}
$extensions = null;
$condition = null;
for ($i = 0; $i < count($matches[0]); $i++) {
$key = $matches['key'][$i];
$value = $matches['value'][$i];
switch ($key) {
case "extensions":
if ($token->getSubMatch('type') === 'FILE') {
throw new ParseError("FILE includes may not have an 'extension' attribute", 0, $token->getLine());
}
$extensions = $value;
break;
case "condition":
$condition = $value;
break;
default:
throw new ParseError("unknown attribute '$key' found in INCLUDE statement", 0, $token->getLine());
}
}
return [$extensions, $condition];
}
/**
* @param ParserState $state
* @throws ParseError
*/
private function parseValueOperation(ParserState $state): void
{
switch ($state->token(1)->getType()) {
case TokenInterface::TYPE_OPERATOR_ASSIGNMENT:
$this->parseAssignment($state);
break;
case TokenInterface::TYPE_OPERATOR_COPY:
case TokenInterface::TYPE_OPERATOR_REFERENCE:
$this->parseCopyOrReference($state);
break;
case TokenInterface::TYPE_OPERATOR_MODIFY:
$this->parseModification($state);
break;
case TokenInterface::TYPE_OPERATOR_DELETE:
$this->parseDeletion($state);
break;
case TokenInterface::TYPE_RIGHTVALUE_MULTILINE:
$this->parseMultilineAssigment($state);
break;
}
}
/**
* @param ParserState $state
*/
private function parseAssignment(ParserState $state): void
{
switch ($state->token(2)->getType()) {
case TokenInterface::TYPE_OBJECT_CONSTRUCTOR:
$state->statements()->append($this->builder->op()->objectCreation(
$state->context(),
$this->builder->scalar($state->token(2)->getValue()),
$state->token(2)->getLine()
));
$state->next(2);
break;
case TokenInterface::TYPE_RIGHTVALUE:
$state->statements()->append($this->builder->op()->assignment(
$state->context(),
$this->builder->scalar($state->token(2)->getValue()),
$state->token(2)->getLine()
));
$state->next(2);
break;
case TokenInterface::TYPE_WHITESPACE:
$state->statements()->append($this->builder->op()->assignment(
$state->context(),
$this->builder->scalar(''),
$state->token()->getLine()
));
$state->next();
break;
}
}
/**
* @param ParserState $state
* @throws ParseError
*/
private function parseCopyOrReference(ParserState $state): void
{
$targetToken = $state->token(2);
$this->validateCopyOperatorRightValue($targetToken);
$target = $state->context()->parent()->append($targetToken->getValue());
$type = ($state->token(1)->getType() === TokenInterface::TYPE_OPERATOR_COPY) ? 'copy' : 'reference';
$node = $this->builder->op()->{$type}(
$state->context(),
$target,
$state->token(1)->getLine()
);
$state->statements()->append($node);
$state->next(2);
}
/**
* @param ParserState $state
* @throws ParseError
*/
private function parseModification(ParserState $state): void
{
$token = $state->token(2);
$this->validateModifyOperatorRightValue($token);
$call = $this->builder->op()->modificationCall(
$token->getSubMatch('name'),
$token->getSubMatch('arguments')
);
$modification = $this->builder->op()->modification(
$state->context(),
$call,
$token->getLine()
);
$state->statements()->append($modification);
$state->next(2);
}
/**
* @param ParserState $state
* @throws ParseError
*/
private function parseDeletion(ParserState $state): void
{
if ($state->token(2)->getType() !== TokenInterface::TYPE_WHITESPACE) {
throw new ParseError(
'Unexpected token ' . $state->token(2)->getType() . ' after delete operator (expected line break).',
1403011201,
$state->token()->getLine()
);
}
$state->statements()->append($this->builder->op()->delete($state->context(), $state->token(1)->getLine()));
$state->next(1);
}
/**
* @param ParserState $state
*/
private function parseMultilineAssigment(ParserState $state): void
{
$state->statements()->append($this->builder->op()->assignment(
$state->context(),
$this->builder->scalar($state->token(1)->getValue()),
$state->token(1)->getLine()
));
$state->next();
}
/**
* @param TokenInterface $token
* @throws ParseError
*/
private function validateModifyOperatorRightValue(TokenInterface $token): void
{
if ($token->getType() !== TokenInterface::TYPE_OBJECT_MODIFIER) {
throw new ParseError(
'Unexpected token ' . $token->getType() . ' after modify operator.',
1403010294,
$token->getLine()
);
}
}
/**
* @param TokenInterface $token
* @throws ParseError
*/
private function validateCopyOperatorRightValue(TokenInterface $token): void
{
if ($token->getType() !== TokenInterface::TYPE_OBJECT_IDENTIFIER) {
throw new ParseError(
'Unexpected token ' . $token->getType() . ' after copy operator.',
1403010294,
$token->getLine()
);
}
}
}
<?php declare(strict_types=1);
namespace Helmich\TypoScriptParser\Parser;
use Helmich\TypoScriptParser\Parser\AST\Statement;
interface ParserInterface
{
/**
* Parses a stream resource.
*
* This can be any kind of stream supported by PHP (e.g. a filename or a URL).
*
* @param string $stream The stream resource.
* @return Statement[] The syntax tree.
*/
public function parseStream(string $stream): array;
/**
* Parses a TypoScript string.
*
* @param string $string The string to parse.
* @return Statement[] The syntax tree.
*/
public function parseString(string $string): array;
/**
* Parses a token stream.
*
* @param \Helmich\TypoScriptParser\Tokenizer\TokenInterface[] $tokens The token stream to parse.
* @return Statement[] The syntax tree.
*/
public function parseTokens(array $tokens): array;
}
<?php declare(strict_types=1);
namespace Helmich\TypoScriptParser\Parser;
use Exception;
class ParseError extends \Exception
{
/** @var int|null */
private $sourceLine;
public function __construct(string $message = "", int $code = 0, ?int $line = null, Exception $previous = null)
{
parent::__construct($message, $code, $previous);
$this->sourceLine = $line;
}
public function getSourceLine(): ?int
{
return $this->sourceLine;
}
}
<?php declare(strict_types=1);
namespace Helmich\TypoScriptParser\Parser;
use ArrayObject;
use Helmich\TypoScriptParser\Parser\AST\ObjectPath;
use Helmich\TypoScriptParser\Parser\AST\RootObjectPath;
use Helmich\TypoScriptParser\Tokenizer\TokenInterface;
class ParserState
{
/** @var ObjectPath */
private $context;
/** @var ArrayObject */
private $statements;
/** @var TokenStream */
private $tokens;
public function __construct(TokenStream $tokens, ArrayObject $statements = null)
{
if ($statements === null) {
$statements = new ArrayObject();
}
$this->statements = $statements;
$this->tokens = $tokens;
$this->context = new RootObjectPath();
}
public function withContext(ObjectPath $context): self
{
$clone = clone $this;
$clone->context = $context;
return $clone;
}
public function withStatements(ArrayObject $statements): self
{
$clone = clone $this;
$clone->statements = $statements;
return $clone;
}
/**
* @param int $lookAhead
* @return TokenInterface
*/
public function token(int $lookAhead = 0): TokenInterface
{
return $this->tokens->current($lookAhead);
}
/**
* @param int $increment
* @return void
*/
public function next(int $increment = 1): void
{
$this->tokens->next($increment);
}
/**
* @return bool
*/
public function hasNext(): bool
{
return $this->tokens->valid();
}
/**
* @return ObjectPath
*/
public function context(): ObjectPath
{
return $this->context;
}
/**
* @return ArrayObject
*/
public function statements(): ArrayObject
{
return $this->statements;
}
}
<?php declare(strict_types=1);
namespace Helmich\TypoScriptParser\Parser\Traverser;
use Helmich\TypoScriptParser\Parser\AST\ConditionalStatement;
use Helmich\TypoScriptParser\Parser\AST\NestedAssignment;
use Helmich\TypoScriptParser\Parser\AST\Statement;
/**
* Class Traverser
*
* @package Helmich\TypoScriptParser
* @subpackage Parser\Traverser
*/
class Traverser
{
/** @var Statement[] */
private $statements;
/** @var AggregatingVisitor */
private $visitors;
/**
* @param Statement[] $statements
*/
public function __construct(array $statements)
{
$this->statements = $statements;
$this->visitors = new AggregatingVisitor();
}
/**
* @param Visitor $visitor
*/
public function addVisitor(Visitor $visitor): void
{
$this->visitors->addVisitor($visitor);
}
/**
* @return void
*/
public function walk(): void
{
$this->visitors->enterTree($this->statements);
$this->walkRecursive($this->statements);
$this->visitors->exitTree($this->statements);
}
/**
* @param Statement[] $statements
* @return Statement[]
*/
private function walkRecursive(array $statements): array
{
foreach ($statements as $statement) {
$this->visitors->enterNode($statement);
if ($statement instanceof NestedAssignment) {
$statement->statements = $this->walkRecursive($statement->statements);
} elseif ($statement instanceof ConditionalStatement) {
$statement->ifStatements = $this->walkRecursive($statement->ifStatements);
$statement->elseStatements = $this->walkRecursive($statement->elseStatements);
}
$this->visitors->exitNode($statement);
}
return $statements;
}
}
<?php declare(strict_types=1);
namespace Helmich\TypoScriptParser\Parser\Traverser;
use Helmich\TypoScriptParser\Parser\AST\Statement;
/**
* Interface Visitor
*
* @package Helmich\TypoScriptParser
* @subpackage Parser\Traverser
*/
interface Visitor
{
/**
* @param Statement[] $statements
* @return void
*/
public function enterTree(array $statements): void;
/**
* @param Statement $statement
* @return void
*/
public function enterNode(Statement $statement): void;
/**
* @param Statement $statement
* @return void
*/
public function exitNode(Statement $statement): void;
/**
* @param Statement[] $statements
* @return void
*/
public function exitTree(array $statements): void;
}
<?php declare(strict_types=1);
namespace Helmich\TypoScriptParser\Parser\Traverser;
use Helmich\TypoScriptParser\Parser\AST\Statement;
/**
* Class AggregatingVisitor
*
* @package Helmich\TypoScriptParser
* @subpackage Parser\Traverser
*/
class AggregatingVisitor implements Visitor
{
/** @var Visitor[] */
private $visitors = [];
/**
* @param Visitor $visitor
* @return void
*/
public function addVisitor(Visitor $visitor): void
{
$this->visitors[spl_object_hash($visitor)] = $visitor;
}
/**
* @param Statement[] $statements
* @return void
*/
public function enterTree(array $statements): void
{
foreach ($this->visitors as $visitor) {
$visitor->enterTree($statements);
}
}
/**
* @param Statement $statement
* @return void
*/
public function enterNode(Statement $statement): void
{
foreach ($this->visitors as $visitor) {
$visitor->enterNode($statement);
}
}
/**
* @param Statement $statement
* @return void
*/
public function exitNode(Statement $statement): void
{
foreach ($this->visitors as $visitor) {
$visitor->exitNode($statement);
}
}
/**
* @param Statement[] $statements
* @return void
*/
public function exitTree(array $statements): void
{
foreach ($this->visitors as $visitor) {
$visitor->exitTree($statements);
}
}
}
<?php declare(strict_types=1);
namespace Helmich\TypoScriptParser\Parser\AST;
/**
* Abstract base class for include statements.
*
* @package Helmich\TypoScriptParser
* @subpackage Parser\AST
*/
abstract class IncludeStatement extends Statement
{
}
<?php declare(strict_types=1);
namespace Helmich\TypoScriptParser\Parser\AST;
/**
* Include statements that includes a single TypoScript file.
*
* @package Helmich\TypoScriptParser
* @subpackage Parser\AST
*/
class FileIncludeStatement extends IncludeStatement
{
/**
* The name of the file to include.
*
* @var string
*/
public $filename;
/**
* Conditional statement that is attached to this include
*
* @var string|null
*/
public $condition;
/**
* Determines if this statement uses the new @import syntax
*
* @var boolean
*/
public $newSyntax;
/**
* Constructs a new include statement.
*
* @param string $filename The name of the file to include.
* @param boolean $newSyntax Determines if this statement uses the new import syntax
* @param string|null $condition Conditional statement that is attached to this include
* @param int $sourceLine The original source line.
*/
public function __construct(string $filename, bool $newSyntax, ?string $condition, int $sourceLine)
{
parent::__construct($sourceLine);
$this->filename = $filename;
$this->newSyntax = $newSyntax;
$this->condition = $condition;
}
}
<?php declare(strict_types=1);
namespace Helmich\TypoScriptParser\Parser\AST;
/**
* A conditional statement with a condition, an if-branch and an optional else-branch.
*
* @package Helmich\TypoScriptParser
* @subpackage Parser\AST
*/
class ConditionalStatement extends Statement
{
/**
* The condition to evaluate.
*
* @var string
*/
public $condition;
/**
* Statements within the if-branch.
*
* @var Statement[]
*/
public $ifStatements = [];
/**
* Statements within the else-branch.
*
* @var Statement[]
*/
public $elseStatements = [];
/**
* Constructs a conditional statement.
*
* @param string $condition The condition statement
* @param Statement[] $ifStatements The statements in the if-branch.
* @param Statement[] $elseStatements The statements in the else-branch (may be empty).
* @param int $sourceLine The original source line.
*/
public function __construct(string $condition, array $ifStatements, array $elseStatements, int $sourceLine)
{
parent::__construct($sourceLine);
$this->condition = $condition;
$this->ifStatements = $ifStatements;
$this->elseStatements = $elseStatements;
}
}
<?php declare(strict_types=1);
namespace Helmich\TypoScriptParser\Parser\AST;
/**
* Class RootObjectPath
*
* @package Helmich\TypoScriptParser
* @subpackage Parser\AST
*/
class RootObjectPath extends ObjectPath
{
/**
* RootObjectPath constructor.
*/
public function __construct()
{
parent::__construct('', '');
}
/**
* @return ObjectPath
*/
public function parent(): ObjectPath
{
return $this;
}
/**
* @return int
*/
public function depth(): int
{
return 0;
}
/**
* @param string $name
* @return ObjectPath
*/
public function append($name): ObjectPath
{
return new ObjectPath(ltrim($name, '.'), $name);
}
}
<?php declare(strict_types=1);
namespace Helmich\TypoScriptParser\Parser\AST;
/**
* Helper class for quickly building AST nodes
*
* @package Helmich\TypoScriptParser
* @subpackage Parser\AST
*/
class Builder
{
/** @var Operator\Builder */
private $operatorBuilder;
/**
* Builder constructor.
*/
public function __construct()
{
$this->operatorBuilder = new Operator\Builder();
}
/**
* @param string $condition
* @param Statement[] $if
* @param Statement[] $else
* @param int $line
* @return ConditionalStatement
*/
public function condition(string $condition, array $if, array $else, int $line): ConditionalStatement
{
return new ConditionalStatement($condition, $if, $else, $line);
}
/**
* @param string $directory
* @param string|null $extensions
* @param string|null $condition
* @param int $line
* @return DirectoryIncludeStatement
*/
public function includeDirectory(string $directory, ?string $extensions, ?string $condition, int $line): DirectoryIncludeStatement
{
return new DirectoryIncludeStatement($directory, $extensions, $condition, $line);
}
/**
* @param string $file
* @param boolean $newSyntax
* @param string|null $condition
* @param int $line
* @return FileIncludeStatement
*/
public function includeFile(string $file, bool $newSyntax, ?string $condition, int $line): FileIncludeStatement
{
return new FileIncludeStatement($file, $newSyntax, $condition, $line);
}
/**
* @param ObjectPath $path
* @param Statement[] $statements
* @param int $line
* @return NestedAssignment
*/
public function nested(ObjectPath $path, array $statements, int $line): NestedAssignment
{
return new NestedAssignment($path, $statements, $line);
}
/**
* @param string $value
* @return Scalar
*/
public function scalar(string $value): Scalar
{
return new Scalar($value);
}
/**
* @param string $absolute
* @param string $relative
* @return ObjectPath
*/
public function path(string $absolute, string $relative): ObjectPath
{
return new ObjectPath($absolute, $relative);
}
/**
* @return Operator\Builder
*/
public function op(): Operator\Builder
{
return $this->operatorBuilder;
}
}
<?php declare(strict_types=1);
namespace Helmich\TypoScriptParser\Parser\AST;
/**
* A scalar value.
*
* @package Helmich\TypoScriptParser
* @subpackage Parser\AST
*/
class Scalar
{
/**
* The value.
*
* @var string
*/
public $value;
/**
* Constructs a scalar value.
*
* @param string $value The value.
*/
public function __construct(string $value)
{
$this->value = $value;
}
}
<?php declare(strict_types=1);
namespace Helmich\TypoScriptParser\Parser\AST\Operator;
use Helmich\TypoScriptParser\Parser\AST\ObjectPath;
use Helmich\TypoScriptParser\Parser\AST\Statement;
/**
* Abstract base class for statements with binary operators.
*
* @package Helmich\TypoScriptParser
* @subpcakage Parser\AST\Operator
*/
abstract class BinaryOperator extends Statement
{
/**
* The object on the left-hand side of the statement.
*
* @var ObjectPath
*/
public $object;
}
<?php declare(strict_types=1);
namespace Helmich\TypoScriptParser\Parser\AST\Operator;
use Helmich\TypoScriptParser\Parser\AST\ObjectPath;
/**
* A copy assignment.
*
* Example:
*
* foo = bar
* baz < foo
*
* @package Helmich\TypoScriptParser
* @subpackage Parser\AST\Operator
*/
class Copy extends BinaryObjectOperator
{
/**
* Constructs a copy statement.
*
* @param ObjectPath $object The object to copy the value to.
* @param ObjectPath $target The object to copy the value from.
* @param int $sourceLine The original source line.
*/
public function __construct(ObjectPath $object, ObjectPath $target, int $sourceLine)
{
parent::__construct($sourceLine);
$this->object = $object;
$this->target = $target;
}
}
<?php declare(strict_types=1);
namespace Helmich\TypoScriptParser\Parser\AST\Operator;
use Helmich\TypoScriptParser\Parser\AST\ObjectPath;
/**
* Abstract base class for statements with binary operators.
*
* @package Helmich\TypoScriptParser
* @subpackage Parser\AST\Operator
*/
abstract class BinaryObjectOperator extends BinaryOperator
{
/**
* The target object to reference to or copy from.
*
* @var ObjectPath
*/
public $target;
}
<?php declare(strict_types=1);
namespace Helmich\TypoScriptParser\Parser\AST\Operator;
use Helmich\TypoScriptParser\Parser\AST\ObjectPath;
/**
* A modification statement.
*
* Example:
*
* foo = bar
* foo := appendToString(baz)
*
* @package Helmich\TypoScriptParser
* @subpackage Parser\AST\Operator
*/
class Modification extends BinaryOperator
{
/**
* The modification call.
*
* @var ModificationCall
*/
public $call;
/**
* Constructs a modification statement.
*
* @param ObjectPath $object The object to modify.
* @param ModificationCall $call The modification call.
* @param int $sourceLine The original source line.
*/
public function __construct(ObjectPath $object, ModificationCall $call, int $sourceLine)
{
parent::__construct($sourceLine);
$this->object = $object;
$this->call = $call;
}
}
<?php declare(strict_types=1);
namespace Helmich\TypoScriptParser\Parser\AST\Operator;
/**
* A modification call (usually on the right-hand side of a modification statement).
*
* @package Helmich\TypoScriptParser
* @subpackage Parser\AST\Operator
*/
class ModificationCall
{
/**
* The method name.
*
* @var string
*/
public $method;
/**
* The argument list.
*
* @var string
*/
public $arguments;
/**
* Modification call constructor.
*
* @param string $method The method name.
* @param string $arguments The argument list.
*/
public function __construct(string $method, string $arguments)
{
$this->arguments = $arguments;
$this->method = $method;
}
}
<?php declare(strict_types=1);
namespace Helmich\TypoScriptParser\Parser\AST\Operator;
/**
* Helper class for quickly building operator AST nodes
*
* @package Helmich\TypoScriptParser
* @subpackage Parser\AST\Operator
*
* @method ObjectCreation objectCreation($path, $value, $line)
* @method Assignment assignment($path, $value, $line)
* @method Copy copy($path, $value, $line)
* @method Reference reference($path, $value, $line)
* @method Delete delete($path, $line)
* @method ModificationCall modificationCall($method, $arguments)
* @method Modification modification($path, $call, $line)
*/
class Builder
{
public function __call(string $name, array $args)
{
$class = __NAMESPACE__ . '\\' . ucfirst($name);
return new $class(...$args);
}
}
<?php declare(strict_types=1);
namespace Helmich\TypoScriptParser\Parser\AST\Operator;
use Helmich\TypoScriptParser\Parser\AST\ObjectPath;
use Helmich\TypoScriptParser\Parser\AST\Scalar;
/**
* An assignment statement.
*
* Example:
*
* foo = bar
*
* @package Helmich\TypoScriptParser
* @subpackage Parser\AST\Operator
*/
class Assignment extends BinaryOperator
{
/**
* The value to be assigned. Should be a scalar value, which MAY contain
* a constant evaluation expression (like "${foo.bar}").
*
* @var Scalar
*/
public $value;
/**
* Constructs an assignment.
*
* @param ObjectPath $object The object to which to assign the value.
* @param Scalar $value The value to be assigned.
* @param int $sourceLine The source line.
*/
public function __construct(ObjectPath $object, Scalar $value, int $sourceLine)
{
parent::__construct($sourceLine);
$this->object = $object;
$this->value = $value;
}
}
<?php declare(strict_types=1);
namespace Helmich\TypoScriptParser\Parser\AST\Operator;
use Helmich\TypoScriptParser\Parser\AST\ObjectPath;
/**
* A reference statement.
*
* Example:
*
* foo = bar
* baz <= foo
*
* @package Helmich\TypoScriptParser
* @subpackage Parser\AST\Operator
*/
class Reference extends BinaryObjectOperator
{
/**
* Constructs a new reference statement.
*
* @param ObjectPath $object The reference object.
* @param ObjectPath $target The target object.
* @param int $sourceLine The original source line.
*/
public function __construct(ObjectPath $object, ObjectPath $target, int $sourceLine)
{
parent::__construct($sourceLine);
$this->object = $object;
$this->target = $target;
}
}
<?php declare(strict_types=1);
namespace Helmich\TypoScriptParser\Parser\AST\Operator;
/**
* Object creation statement.
*
* Example:
*
* foo = TEXT
*
* @package Helmich\TypoScriptParser
* @subpackage Parser\AST\Operator
*/
class ObjectCreation extends Assignment
{
}
<?php declare(strict_types=1);
namespace Helmich\TypoScriptParser\Parser\AST\Operator;
use Helmich\TypoScriptParser\Parser\AST\ObjectPath;
use Helmich\TypoScriptParser\Parser\AST\Statement;
/**
* Abstract base class for statements with unary operators.
*
* @package Helmich\TypoScriptParser
* @subpackage Parser\AST\Operator
*/
abstract class UnaryOperator extends Statement
{
/**
* The object the operator should be applied on.
*
* @var ObjectPath
*/
public $object;
/**
* Constructs a unary operator statement.
*
* @param ObjectPath $object The object to operate on.
* @param int $sourceLine The original source line.
*/
public function __construct(ObjectPath $object, int $sourceLine)
{
parent::__construct($sourceLine);
$this->object = $object;
}
}
<?php declare(strict_types=1);
namespace Helmich\TypoScriptParser\Parser\AST\Operator;
/**
* A delete operator.
*
* Example:
*
* foo >
*
* @package Helmich\TypoScriptParser
* @subpackage Parser\AST\Operator
*/
class Delete extends UnaryOperator
{
}
<?php declare(strict_types=1);
namespace Helmich\TypoScriptParser\Parser\AST;
/**
* An object path.
*
* @package Helmich\TypoScriptParser
* @subpackage Parser\AST
*/
class ObjectPath
{
/**
* The relative object path, as specified in the source code.
*
* @var string
*/
public $relativeName;
/**
* The absolute object path, as evaluated from parent nested statements.
*
* @var string
*/
public $absoluteName;
/**
* Constructs a new object path.
*
* @param string $absoluteName The absolute object path.
* @param string $relativeName The relative object path.
*/
public function __construct(string $absoluteName, string $relativeName)
{
$this->absoluteName = $absoluteName;
$this->relativeName = $relativeName;
}
/**
* @return int
*/
public function depth(): int
{
return count(explode('.', $this->absoluteName));
}
/**
* Builds the path to the parent object.
*
* @return ObjectPath The path to the parent object.
*/
public function parent(): ObjectPath
{
$components = explode('.', $this->absoluteName);
if (count($components) === 1) {
return new RootObjectPath();
}
array_pop($components);
return new static(implode('.', $components), $components[count($components) - 1]);
}
/**
* @param string $name
* @return static
*/
public function append(string $name): self
{
if ($name[0] === '.') {
return new static($this->absoluteName . $name, $name);
}
return new static($this->absoluteName . '.' . $name, $name);
}
}
<?php declare(strict_types=1);
namespace Helmich\TypoScriptParser\Parser\AST;
/**
* A nested assignment statement.
*
* Example:
*
* foo {
* bar = 1
* baz = 2
* }
*
* Which is equivalent to
*
* foo.bar = 1
* foo.baz = 2
*
* @package Helmich\TypoScriptParser
* @subpackage Parser\AST
*/
class NestedAssignment extends Statement
{
/**
* The object to operate on.
*
* @var ObjectPath
*/
public $object;
/**
* The nested statements.
*
* @var Statement[]
*/
public $statements;
/**
* @param ObjectPath $object The object to operate on.
* @param Statement[] $statements The nested statements.
* @param int $sourceLine The original source line.
*/
public function __construct(ObjectPath $object, array $statements, int $sourceLine)
{
parent::__construct($sourceLine);
$this->object = $object;
$this->statements = $statements;
}
}
<?php declare(strict_types=1);
namespace Helmich\TypoScriptParser\Parser\AST;
/**
* Include statements that includes many TypoScript files from a directory.
*
* @package Helmich\TypoScriptParser
* @subpackage Parser\AST
*/
class DirectoryIncludeStatement extends IncludeStatement
{
/**
* The directory to include from.
*
* @var string
*/
public $directory;
/**
* Conditional statement that is attached to this include
*
* @var string|null
*/
public $condition;
/**
* Same as extensions
*
* @var string|null
* @deprecated Use `extensions` instead
*/
public $extension = null;
/**
* An optional file extension filter. May be NULL.
*
* @var string|null
*/
public $extensions = null;
/**
* Constructs a new directory include statement.
*
* @param string $directory The directory to include from.
* @param string|null $extensions The file extension filter. MAY be NULL.
* @param string|null $condition Conditional statement that is attached to this include
* @param int $sourceLine The original source line.
*
* @psalm-suppress DeprecatedProperty
*/
public function __construct(string $directory, ?string $extensions, ?string $condition, int $sourceLine)
{
parent::__construct($sourceLine);
$this->directory = $directory;
$this->extension = $extensions;
$this->extensions = $extensions;
$this->condition = $condition;
}
}
<?php declare(strict_types=1);
namespace Helmich\TypoScriptParser\Parser\AST;
/**
* Abstract TypoScript statement.
*
* @package Helmich\TypoScriptParser
* @subpackage Parser\AST
*/
abstract class Statement
{
/**
* The original source line. Useful for tracing and debugging.
*
* @var int
*/
public $sourceLine;
/**
* Base statement constructor.
*
* @param int $sourceLine The original source line.
*/
public function __construct(int $sourceLine)
{
if ($sourceLine <= 0) {
throw new \InvalidArgumentException(
sprintf('Source line must be greater than 0 for %s statement (is: %d)!', get_class($this), $sourceLine)
);
}
$this->sourceLine = $sourceLine;
}
}
<?php declare(strict_types=1);
namespace Helmich\TypoScriptParser\Tokenizer\Printer;
use Helmich\TypoScriptParser\Tokenizer\TokenInterface;
/**
* Interface definition for a class that prints token streams
*
* @package Helmich\TypoScriptParser
* @subpackage Tokenizer\Printer
*/
interface TokenPrinterInterface
{
/**
* @param TokenInterface[] $tokens
* @return string
*/
public function printTokenStream(array $tokens): string;
}
<?php declare(strict_types=1);
namespace Helmich\TypoScriptParser\Tokenizer\Printer;
use Helmich\TypoScriptParser\Tokenizer\TokenInterface;
class CodeTokenPrinter implements TokenPrinterInterface
{
/**
* @param TokenInterface[] $tokens
* @return string
*/
public function printTokenStream(array $tokens): string
{
$content = '';
foreach ($tokens as $token) {
$content .= $token->getValue();
}
return $content;
}
}
<?php declare(strict_types=1);
namespace Helmich\TypoScriptParser\Tokenizer\Printer;
use Helmich\TypoScriptParser\Tokenizer\TokenInterface;
use Symfony\Component\Yaml\Yaml;
class StructuredTokenPrinter implements TokenPrinterInterface
{
/** @var Yaml */
private $yaml;
public function __construct(Yaml $yaml = null)
{
$this->yaml = $yaml ?: new Yaml();
}
/**
* @param TokenInterface[] $tokens
* @return string
*/
public function printTokenStream(array $tokens): string
{
$content = '';
foreach ($tokens as $token) {
$content .= sprintf("%20s %s\n", $token->getType(), $this->yaml->dump($token->getValue()));
}
return $content;
}
}
<?php declare(strict_types=1);
namespace Helmich\TypoScriptParser\Tokenizer\Preprocessing;
/**
* Preprocessor that does not actually do anything
*
* @package Helmich\TypoScriptParser\Tokenizer\Preprocessing
*/
class NoOpPreprocessor implements Preprocessor
{
/**
* @param string $contents Un-processed Typoscript contents
* @return string Processed TypoScript contents
*/
public function preprocess(string $contents): string
{
return $contents;
}
}
<?php declare(strict_types=1);
namespace Helmich\TypoScriptParser\Tokenizer\Preprocessing;
/**
* Helper class that provides the standard pre-processing behaviour
*
* @package Helmich\TypoScriptParser\Tokenizer\Preprocessing
*/
class StandardPreprocessor extends ProcessorChain
{
public function __construct(string $eolChar = "\n")
{
$this->processors = [
new UnifyLineEndingsPreprocessor($eolChar),
new RemoveTrailingWhitespacePreprocessor($eolChar),
];
}
}
<?php declare(strict_types=1);
namespace Helmich\TypoScriptParser\Tokenizer\Preprocessing;
/**
* Preprocessor that removes trailing whitespaces from a file
*
* @package Helmich\TypoScriptParser\Tokenizer\Preprocessing
*/
class RemoveTrailingWhitespacePreprocessor implements Preprocessor
{
/** @var string */
private $eolCharacter;
public function __construct(string $eolCharacter = "\n")
{
$this->eolCharacter = $eolCharacter;
}
/**
* @param string $contents Un-processed Typoscript contents
* @return string Processed TypoScript contents
*/
public function preprocess(string $contents): string
{
// Remove trailing whitespaces.
$lines = explode($this->eolCharacter, $contents);
$lines = array_map('rtrim', $lines);
$content = implode($this->eolCharacter, $lines);
return $content;
}
}
<?php declare(strict_types=1);
namespace Helmich\TypoScriptParser\Tokenizer\Preprocessing;
/**
* Preprocessor that unifies line endings for a file
*
* @package Helmich\TypoScriptParser\Tokenizer\Preprocessing
*/
class UnifyLineEndingsPreprocessor implements Preprocessor
{
/** @var string */
private $eolCharacter;
public function __construct(string $eolCharacter = "\n")
{
$this->eolCharacter = $eolCharacter;
}
/**
* @param string $contents Un-processed Typoscript contents
* @return string Processed TypoScript contents
*/
public function preprocess(string $contents): string
{
return preg_replace(",(\r\n|\r|\n),", $this->eolCharacter, $contents);
}
}
<?php declare(strict_types=1);
namespace Helmich\TypoScriptParser\Tokenizer\Preprocessing;
/**
* Preprocessor that combines multiple preprocessors
*
* @package Helmich\TypoScriptParser\Tokenizer\Preprocessing
*/
class ProcessorChain implements Preprocessor
{
/** @var Preprocessor[] */
protected $processors = [];
/**
* @param Preprocessor $next
* @return static
*/
public function with(Preprocessor $next): self
{
$new = new static();
$new->processors = array_merge($this->processors, [$next]);
return $new;
}
/**
* @param string $contents Un-processed Typoscript contents
* @return string Processed TypoScript contents
*/
public function preprocess(string $contents): string
{
foreach ($this->processors as $p) {
$contents = $p->preprocess($contents);
}
return $contents;
}
}
<?php declare(strict_types=1);
namespace Helmich\TypoScriptParser\Tokenizer\Preprocessing;
/**
* Interface definitions for tokenizer preprocessors.
*
* Preprocessors can change the TypoScript input source code before it
* is passed to the actual tokenizer.
*
* @package Helmich\TypoScriptParser\Tokenizer\Preprocessing
*/
interface Preprocessor
{
/**
* @param string $contents Un-processed Typoscript contents
* @return string Processed TypoScript contents
*/
public function preprocess(string $contents): string;
}
<?php declare(strict_types=1);
namespace Helmich\TypoScriptParser\Tokenizer;
/**
* Interface TokenizerInterface
*
* @package Helmich\TypoScriptParser
* @subpackage Tokenizer
*/
interface TokenizerInterface
{
/**
* @param string $inputString
* @return TokenInterface[]
*/
public function tokenizeString(string $inputString): array;
/**
* @param string $inputStream
* @return TokenInterface[]
*/
public function tokenizeStream(string $inputStream): array;
}
<?php declare(strict_types=1);
namespace Helmich\TypoScriptParser\Tokenizer;
interface TokenInterface
{
const TYPE_WHITESPACE = "WS";
const TYPE_COMMENT_MULTILINE = "COMMENT_MULTILINE";
const TYPE_COMMENT_ONELINE = "COMMENT";
const TYPE_RIGHTVALUE_MULTILINE = "RVALUE_MULTILINE";
const TYPE_RIGHTVALUE = "RVALUE";
const TYPE_BRACE_OPEN = "BR_OPEN";
const TYPE_BRACE_CLOSE = "BR_CLOSE";
const TYPE_CONDITION = "COND";
const TYPE_CONDITION_ELSE = "COND_ELSE";
const TYPE_CONDITION_END = "COND_END";
const TYPE_OBJECT_IDENTIFIER = "OBJ_IDENT";
const TYPE_OBJECT_CONSTRUCTOR = "OBJ_CONTRUCT";
const TYPE_OBJECT_MODIFIER = "OBJ_MODIFIER";
const TYPE_OPERATOR_ASSIGNMENT = "OP_ASSIGN";
const TYPE_OPERATOR_MODIFY = "OP_MODIFY";
const TYPE_OPERATOR_COPY = "OP_COPY";
const TYPE_OPERATOR_REFERENCE = "OP_REF";
const TYPE_OPERATOR_DELETE = "OP_DELETE";
const TYPE_INCLUDE = "INCLUDE";
const TYPE_INCLUDE_NEW = "INCLUDE_NEW";
public function getType(): string;
public function getValue(): string;
public function getSubMatch(string $name): ?string;
public function getLine(): int;
public function getColumn(): int;
}
<?php declare(strict_types=1);
namespace Helmich\TypoScriptParser\Tokenizer;
class Token implements TokenInterface
{
/** @var string */
private $type;
/** @var string */
private $value;
/** @var int */
private $line;
/** @var int */
private $column;
/** @var array */
private $patternMatches;
/**
* @param string $type
* @param string $value
* @param int $line
* @param int $column
* @param array $patternMatches
*/
public function __construct(string $type, string $value, int $line, int $column = 1, array $patternMatches = [])
{
$this->type = $type;
$this->value = $value;
$this->line = $line;
$this->column = $column;
$this->patternMatches = $patternMatches;
}
/**
* @return string
*/
public function getType(): string
{
return $this->type;
}
/**
* @return string
*/
public function getValue(): string
{
return $this->value;
}
/**
* @param string $string
* @return string|null
*/
public function getSubMatch(string $string): ?string
{
return isset($this->patternMatches[$string]) ? $this->patternMatches[$string] : null;
}
/**
* @return int
*/
public function getLine(): int
{
return $this->line;
}
/**
* @return int
*/
public function getColumn(): int
{
return $this->column;
}
}
<?php declare(strict_types=1);
namespace Helmich\TypoScriptParser\Tokenizer;
use Helmich\TypoScriptParser\Tokenizer\Preprocessing\Preprocessor;
use Helmich\TypoScriptParser\Tokenizer\Preprocessing\StandardPreprocessor;
class Tokenizer implements TokenizerInterface
{
const TOKEN_WHITESPACE = ',^[ \t\n]+,s';
const TOKEN_COMMENT_ONELINE = ',^(#|/)[^\n]*,';
const TOKEN_COMMENT_MULTILINE_BEGIN = ',^/\*,';
const TOKEN_COMMENT_MULTILINE_END = ',^\*/,';
const TOKEN_CONDITION = ',^(\[(?<expr>.*?)\](\|\||&&|$))+,';
const TOKEN_CONDITION_ELSE = ',^\[else\],i';
const TOKEN_CONDITION_END = ',^\[(global|end)\],i';
const TOKEN_OBJECT_NAME = ',^(CASE|CLEARGIF|COA(?:_INT)?|COBJ_ARRAY|COLUMNS|CTABLE|EDITPANEL|FILES?|FLUIDTEMPLATE|FORM|HMENU|HRULER|TEXT|IMAGE|IMG_RESOURCE|IMGTEXT|LOAD_REGISTER|MEDIA|MULTIMEDIA|OTABLE|QTOBJECT|RECORDS|RESTORE_REGISTER|SEARCHRESULT|SVG|SWFOBJECT|TEMPLATE|USER(?:_INT)?|GIFBUILDER|[GT]MENU(?:_LAYERS)?|(?:G|T|JS|IMG)MENUITEM)$,';
const TOKEN_OBJECT_ACCESSOR = ',^([a-zA-Z0-9_\-\\\\:\$\{\}]+(?:\.[a-zA-Z0-9_\-\\\\:\$\{\}]+)*)$,';
const TOKEN_OBJECT_REFERENCE = ',^\.?([a-zA-Z0-9_\-\\\\:\$\{\}]+(?:\.[a-zA-Z0-9_\-\\\\:\$\{\}]+)*)$,';
const TOKEN_NESTING_START = ',^\{$,';
const TOKEN_NESTING_END = ',^\}$,';
const TOKEN_OBJECT_MODIFIER = ',^
(?<name>[a-zA-Z0-9]+) # Modifier name
(?:\s)*
\(
(?<arguments>[^\)]*) # Argument list
\)
$,x';
const TOKEN_OPERATOR_LINE = ',^
([a-zA-Z0-9_\-\\\\:\$\{\}]+(?:\.[a-zA-Z0-9_\-\\\\:\$\{\}]+)*) # Left value (object accessor)
(\s*) # Whitespace
(=<|=|:=|<|>|\{|\() # Operator
(\s*) # More whitespace
(.*?) # Right value
$,x';
const TOKEN_INCLUDE_STATEMENT = ',^
<INCLUDE_TYPOSCRIPT:\s*
source="(?<type>FILE|DIR):(?<filename>[^"]+)"\s*
(?<optional>.*)
\s*>
$,x';
const TOKEN_INCLUDE_NEW_STATEMENT = ',^
@import\s+
[\'"](?<filename>[^\']+)[\'"]
$,x';
/** @var string */
protected $eolChar;
/** @var Preprocessor */
protected $preprocessor;
/**
* Tokenizer constructor.
*
* @param string $eolChar Line ending to use for tokenizing.
* @param Preprocessor|null $preprocessor Option to preprocess file contents before actual tokenizing
*/
public function __construct(string $eolChar = "\n", ?Preprocessor $preprocessor = null)
{
if ($preprocessor === null) {
$preprocessor = new StandardPreprocessor($eolChar);
}
$this->eolChar = $eolChar;
$this->preprocessor = $preprocessor;
}
/**
* @param string $inputString
* @throws TokenizerException
* @return TokenInterface[]
*/
public function tokenizeString(string $inputString): array
{
$inputString = $this->preprocessor->preprocess($inputString);
$tokens = new TokenStreamBuilder();
$state = new MultilineTokenBuilder();
$lines = explode($this->eolChar, $inputString);
$scanner = new Scanner($lines);
foreach ($scanner as $line) {
$column = 1;
if ($this->tokenizeMultilineToken($tokens, $state, $line)) {
continue;
}
if ($tokens->count() !== 0) {
$tokens->append(TokenInterface::TYPE_WHITESPACE, $this->eolChar, (int)($line->index() - 1));
$column += 1;
}
if ($matches = $line->scan(self::TOKEN_WHITESPACE)) {
$tokens->append(TokenInterface::TYPE_WHITESPACE, $matches[0], $line->index());
$column += strlen($matches[0]);
}
if ($line->peek(self::TOKEN_COMMENT_MULTILINE_BEGIN)) {
$state->startMultilineToken(TokenInterface::TYPE_COMMENT_MULTILINE, $line->value(), $line->index(), $column);
continue;
}
if ($this->tokenizeSimpleStatements($tokens, $line) ||
$this->tokenizeObjectOperation($tokens, $state, $line) ||
$line->length() === 0) {
continue;
}
throw new TokenizerException('Cannot tokenize line "' . $line . '"', 1403084444, null, $line->index());
}
$currentTokenType = $state->currentTokenType();
if ($currentTokenType !== null) {
throw new TokenizerException(
"Unterminated {$currentTokenType}!",
1403084445,
null,
count($lines) - 1
);
}
return $tokens->build()->getArrayCopy();
}
/**
* @param string $inputStream
* @return TokenInterface[]
*/
public function tokenizeStream(string $inputStream): array
{
$content = file_get_contents($inputStream);
if ($content === false) {
throw new \InvalidArgumentException("could not open file '${inputStream}'");
}
return $this->tokenizeString($content);
}
/**
* @param string $operator
* @return string
* @throws UnknownOperatorException
*/
private function getTokenTypeForBinaryOperator(string $operator): string
{
switch ($operator) {
case '=':
return TokenInterface::TYPE_OPERATOR_ASSIGNMENT;
case '<':
return TokenInterface::TYPE_OPERATOR_COPY;
case '=<':
return TokenInterface::TYPE_OPERATOR_REFERENCE;
case ':=':
return TokenInterface::TYPE_OPERATOR_MODIFY;
case '>':
return TokenInterface::TYPE_OPERATOR_DELETE;
}
// It should not be possible in any case to reach this point
// @codeCoverageIgnoreStart
throw new UnknownOperatorException('Unknown binary operator "' . $operator . '"!');
// @codeCoverageIgnoreEnd
}
/**
* @param $tokens
* @param $matches
* @param $currentLine
* @throws UnknownOperatorException
*/
private function tokenizeBinaryObjectOperation(TokenStreamBuilder $tokens, array $matches, int $currentLine): void
{
$tokens->append(
$this->getTokenTypeForBinaryOperator($matches[3]),
$matches[3],
$currentLine
);
if ($matches[4]) {
$tokens->append(TokenInterface::TYPE_WHITESPACE, $matches[4], $currentLine);
}
if (($matches[3] === '<' || $matches[3] === '=<') && preg_match(self::TOKEN_OBJECT_REFERENCE, $matches[5])) {
$tokens->append(
TokenInterface::TYPE_OBJECT_IDENTIFIER,
$matches[5],
$currentLine
);
return;
}
if ($matches[3] == ':=' && preg_match(self::TOKEN_OBJECT_MODIFIER, $matches[5], $subMatches)) {
$tokens->append(
TokenInterface::TYPE_OBJECT_MODIFIER,
$matches[5],
$currentLine,
$subMatches
);
return;
}
if (preg_match(self::TOKEN_OBJECT_NAME, $matches[5])) {
$tokens->append(
TokenInterface::TYPE_OBJECT_CONSTRUCTOR,
$matches[5],
$currentLine
);
return;
}
if ($matches[3] == '>' && preg_match(self::TOKEN_COMMENT_ONELINE, $matches[5])) {
$tokens->append(
TokenInterface::TYPE_COMMENT_ONELINE,
$matches[5],
$currentLine
);
return;
}
if (strlen($matches[5])) {
$tokens->append(
TokenInterface::TYPE_RIGHTVALUE,
$matches[5],
$currentLine
);
return;
}
}
/**
* @param TokenStreamBuilder $tokens
* @param MultilineTokenBuilder $state
* @param ScannerLine $line
* @return bool
*/
private function tokenizeMultilineToken(TokenStreamBuilder $tokens, MultilineTokenBuilder $state, ScannerLine $line): bool
{
if ($state->currentTokenType() === TokenInterface::TYPE_COMMENT_MULTILINE) {
$this->tokenizeMultilineComment($tokens, $state, $line);
return true;
}
if ($state->currentTokenType() === TokenInterface::TYPE_RIGHTVALUE_MULTILINE) {
$this->tokenizeMultilineAssignment($tokens, $state, $line);
return true;
}
return false;
}
/**
* @param TokenStreamBuilder $tokens
* @param MultilineTokenBuilder $state
* @param ScannerLine $line
* @return void
*/
private function tokenizeMultilineComment(
TokenStreamBuilder $tokens,
MultilineTokenBuilder $state,
ScannerLine $line
): void {
if ($matches = $line->scan(self::TOKEN_WHITESPACE)) {
$state->appendToToken($matches[0]);
}
if ($matches = $line->peek(self::TOKEN_COMMENT_MULTILINE_END)) {
$token = $state->endMultilineToken($matches[0]);
$tokens->appendToken($token);
return;
}
}
/**
* @param $tokens
* @param $state
* @param $line
*/
private function tokenizeMultilineAssignment(
TokenStreamBuilder $tokens,
MultilineTokenBuilder $state,
ScannerLine $line
): void {
if ($line->peek(',^\s*\),')) {
$token = $state->endMultilineToken();
$tokens->appendToken($token);
return;
}
$state->appendToToken($line . "\n");
}
/**
* @param TokenStreamBuilder $tokens
* @param ScannerLine $line
* @return bool
*/
private function tokenizeSimpleStatements(TokenStreamBuilder $tokens, ScannerLine $line): bool
{
$simpleTokens = [
self::TOKEN_COMMENT_ONELINE => TokenInterface::TYPE_COMMENT_ONELINE,
self::TOKEN_NESTING_END => TokenInterface::TYPE_BRACE_CLOSE,
self::TOKEN_CONDITION_ELSE => TokenInterface::TYPE_CONDITION_ELSE,
self::TOKEN_CONDITION_END => TokenInterface::TYPE_CONDITION_END,
self::TOKEN_CONDITION => TokenInterface::TYPE_CONDITION,
self::TOKEN_INCLUDE_STATEMENT => TokenInterface::TYPE_INCLUDE,
self::TOKEN_INCLUDE_NEW_STATEMENT => TokenInterface::TYPE_INCLUDE_NEW,
];
foreach ($simpleTokens as $pattern => $type) {
if ($matches = $line->scan($pattern)) {
$tokens->append($type, $matches[0], $line->index(), $matches);
return true;
}
}
return false;
}
/**
* @param $tokens
* @param $state
* @param $line
* @return bool
*/
private function tokenizeObjectOperation(
TokenStreamBuilder $tokens,
MultilineTokenBuilder $state,
ScannerLine $line
): bool {
if ($matches = $line->scan(self::TOKEN_OPERATOR_LINE)) {
$tokens->append(TokenInterface::TYPE_OBJECT_IDENTIFIER, $matches[1], $line->index());
if ($matches[2]) {
$tokens->append(TokenInterface::TYPE_WHITESPACE, $matches[2], $line->index());
}
$operators = ['=', ':=', '<', '<=', '>', '=<'];
if (in_array($matches[3], $operators)) {
$this->tokenizeBinaryObjectOperation($tokens, $matches, $line->index());
} elseif ($matches[3] == '{') {
$tokens->append(TokenInterface::TYPE_BRACE_OPEN, $matches[3], $line->index());
} elseif ($matches[3] == '(') {
$state->startMultilineToken(TokenInterface::TYPE_RIGHTVALUE_MULTILINE, '', $line->index(), $tokens->currentColumn());
}
return true;
}
return false;
}
}
<?php declare(strict_types=1);
namespace Helmich\TypoScriptParser\Tokenizer;
/**
* Helper class for building tokens that span multiple lines.
*
* Examples are multi-line comments or "("-assignments.
*
* @package Helmich\TypoScriptParser
* @subpackage Tokenizer
*/
class MultilineTokenBuilder
{
/** @var string|null */
private $type = null;
/** @var string|null */
private $value = null;
/** @var int|null */
private $startLine = null;
/** @var int|null */
private $startColumn = null;
/**
* @param string $type Token type, one of `TokenInterface::TYPE_*`
* @param string $value Token value
* @param int $line Starting line in source code
* @param int $column Starting column in source code
*/
public function startMultilineToken(string $type, string $value, int $line, int $column): void
{
$this->type = $type;
$this->value = $value;
$this->startLine = $line;
$this->startColumn = $column;
}
/**
* @param string $append Token content to append
*/
public function appendToToken(string $append): void
{
if ($this->value === null) {
$this->value = "";
}
$this->value .= $append;
}
/**
* @param string $append Token content to append
* @return TokenInterface
*/
public function endMultilineToken(string $append = ''): TokenInterface
{
$value = ($this->value ?? "") . $append;
$type = $this->type;
$startLine = $this->startLine;
$startColumn = $this->startColumn;
if ($type === null || $startLine === null || $startColumn === null) {
throw new TokenizerException('cannot call "endMultilineToken" before calling "startMultilineToken"');
}
$token = new Token(
$type,
rtrim($value),
$startLine,
$startColumn
);
$this->reset();
return $token;
}
/**
* @return string|null Token type (one of `TokenInterface::TYPE_*`)
*/
public function currentTokenType(): ?string
{
return $this->type;
}
/**
* @return void
*/
private function reset(): void
{
$this->type = null;
$this->value = null;
$this->startLine = null;
$this->startColumn = null;
}
}
<?php declare(strict_types=1);
namespace Helmich\TypoScriptParser\Tokenizer;
/**
* An exception that represents an error during tokenization.
*
* @author Martin Helmich <typo3@martin-helmich.de>
* @license MIT
* @package Helmich\TypoScriptParser
* @subpackage Tokenizer
*/
class TokenizerException extends \Exception
{
/** @var int|null */
private $sourceLine;
/**
* Constructs a new tokenizer exception.
*
* @param string $message The message text.
* @param int $code The exception code.
* @param \Exception|null $previous A nested previous exception.
* @param int|null $sourceLine The original source line.
*/
public function __construct(string $message = "", int $code = 0, \Exception $previous = null, int $sourceLine = null)
{
parent::__construct($message, $code, $previous);
$this->sourceLine = $sourceLine;
}
/**
* Gets the original source line.
*
* @return int|null The original source line.
*/
public function getSourceLine(): ?int
{
return $this->sourceLine;
}
}
<?php declare(strict_types=1);
namespace Helmich\TypoScriptParser\Tokenizer;
use ArrayObject;
/**
* Helper class for building a token stream
*
* @package Helmich\TypoScriptParser
* @subpackage Tokenizer
*/
class TokenStreamBuilder
{
/** @var ArrayObject */
private $tokens;
/** @var int|null */
private $currentLine = null;
/** @var int */
private $currentColumn = 1;
/**
* TokenStreamBuilder constructor.
*/
public function __construct()
{
$this->tokens = new ArrayObject();
}
/**
* Appends a new token to the token stream
*
* @param string $type Token type
* @param string $value Token value
* @param int $line Line in source code
* @param array $patternMatches Subpattern matches
* @return void
*/
public function append(string $type, string $value, int $line, array $patternMatches = []): void
{
if ($this->currentLine !== $line) {
$this->currentLine = $line;
$this->currentColumn = 1;
}
$this->tokens->append(new Token($type, $value, $line, $this->currentColumn, $patternMatches));
$this->currentColumn += strlen($value);
}
/**
* Appends a new token to the token stream
*
* @param TokenInterface $token The token to append
* @return void
*/
public function appendToken(TokenInterface $token): void
{
$this->tokens->append($token);
}
/**
* @return int The length of the token stream
*/
public function count(): int
{
return $this->tokens->count();
}
/**
* @return int
*/
public function currentColumn(): int
{
return $this->currentColumn;
}
/**
* @return ArrayObject The completed token stream
*/
public function build(): ArrayObject
{
return $this->tokens;
}
}
<?php declare(strict_types=1);
namespace Helmich\TypoScriptParser\Tokenizer;
class ScannerLine
{
private $line;
private $index;
private $original;
public function __construct(int $index, string $line)
{
$this->line = $line;
$this->original = $line;
$this->index = $index;
}
/**
* @param string $pattern
* @return array|false
*/
public function scan(string $pattern)
{
if (preg_match($pattern, $this->line, $matches)) {
$this->line = substr($this->line, strlen($matches[0])) ?: "";
return $matches;
}
return false;
}
/**
* @param string $pattern
* @return string[]|false
*/
public function peek(string $pattern)
{
if (preg_match($pattern, $this->line, $matches)) {
return $matches;
}
return false;
}
public function index(): int
{
return $this->index;
}
public function value(): string
{
return $this->line;
}
public function length(): int
{
return strlen($this->line);
}
public function __toString(): string
{
return $this->original;
}
}
<?php declare(strict_types=1);
namespace Helmich\TypoScriptParser\Tokenizer;
use Iterator;
/**
* Helper class for scanning lines
*
* @package Helmich\TypoScriptParser
* @subpackage Tokenizer
*/
class Scanner implements Iterator
{
/** @var string[] */
private $lines = [];
/** @var int */
private $index = 0;
public function __construct(array $lines)
{
$this->lines = $lines;
}
public function current(): ScannerLine
{
return new ScannerLine($this->index + 1, $this->lines[$this->index]);
}
public function next(): void
{
$this->index++;
}
public function key(): int
{
return $this->index;
}
public function valid(): bool
{
return $this->index < count($this->lines);
}
public function rewind(): void
{
$this->index = 0;
}
}
<?php declare(strict_types=1);
namespace Helmich\TypoScriptParser\Tokenizer;
/**
* Class LineGrouper
*
* @package Helmich\TypoScriptParser
* @subpackage Tokenizer
*/
class LineGrouper
{
/** @var TokenInterface[][] */
private $tokensByLine = [];
/**
* @param TokenInterface[] $tokens
*/
public function __construct(array $tokens)
{
foreach ($tokens as $token) {
if (!array_key_exists($token->getLine(), $this->tokensByLine)) {
$this->tokensByLine[$token->getLine()] = [];
}
$this->tokensByLine[$token->getLine()][] = $token;
}
}
/**
* @return TokenInterface[][]
*/
public function getLines(): array
{
return $this->tokensByLine;
}
}
<?php declare(strict_types=1);
namespace Helmich\TypoScriptParser\Tokenizer;
/**
* Exception that is thrown when an unknown operator is encountered.
*
* @author Martin Helmich <typo3@martin-helmich.de>
* @license MIT
* @package Helmich\TypoScriptParser
* @subpackage Tokenizer
*/
class UnknownOperatorException extends \Exception
{
}
<?php declare(strict_types=1);
namespace Helmich\TypoScriptParser;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Extension\ExtensionInterface;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
/**
* Class TypoScriptParserExtension
*
* @package Helmich\TypoScriptParser
* @codeCoverageIgnore
*/
class TypoScriptParserExtension implements ExtensionInterface
{
/**
* Loads a specific configuration.
*
* @param array $config An array of configuration values
* @param ContainerBuilder $container A ContainerBuilder instance
*
* @throws \InvalidArgumentException When provided tag is not defined in this extension
*
* @api
* @psalm-suppress MissingReturnType Signature is determined by Symfony DI -- nothing to fix, here
*/
public function load(array $config, ContainerBuilder $container)
{
$loader = new YamlFileLoader($container, new FileLocator(__DIR__ . '/../config'));
$loader->load('services.yml');
}
/**
* Returns the namespace to be used for this extension (XML namespace).
*
* @return string The XML namespace
*
* @api
*/
public function getNamespace()
{
return 'http://example.org/schema/dic/'.$this->getAlias();
}
/**
* Returns the base path for the XSD files.
*
* @return false
*
* @api
*/
public function getXsdValidationBasePath()
{
return false;
}
/**
* Returns the recommended alias to use in XML.
*
* This alias is also the mandatory prefix to use when using YAML.
*
* @return string The alias
*
* @api
*/
public function getAlias()
{
return 'typoscript_parser';
}
}
The MIT License (MIT)
Copyright (c) 2013-2016 container-interop
Copyright (c) 2016 PHP Framework Interoperability Group
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
<?php
/**
* @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
*/
namespace Psr\Container;
/**
* Base interface representing a generic exception in a container.
*/
interface ContainerExceptionInterface
{
}
<?php
/**
* @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
*/
namespace Psr\Container;
/**
* No entry was found in the container.
*/
interface NotFoundExceptionInterface extends ContainerExceptionInterface
{
}
<?php
/**
* @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
*/
namespace Psr\Container;
/**
* Describes the interface of a container that exposes methods to read its entries.
*/
interface ContainerInterface
{
/**
* Finds an entry of the container by its identifier and returns it.
*
* @param string $id Identifier of the entry to look for.
*
* @throws NotFoundExceptionInterface No entry was found for **this** identifier.
* @throws ContainerExceptionInterface Error while retrieving the entry.
*
* @return mixed Entry.
*/
public function get($id);
/**
* Returns true if the container can return an entry for the given identifier.
* Returns false otherwise.
*
* `has($id)` returning true does not mean that `get($id)` will not throw an exception.
* It does however mean that `get($id)` will not throw a `NotFoundExceptionInterface`.
*
* @param string $id Identifier of the entry to look for.
*
* @return bool
*/
public function has($id);
}
MIT License
Copyright (c) 2018 PHP-FIG
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
<?php
declare(strict_types=1);
namespace Psr\EventDispatcher;
/**
* Defines a dispatcher for events.
*/
interface EventDispatcherInterface
{
/**
* Provide all relevant listeners with an event to process.
*
* @param object $event
* The object to process.
*
* @return object
* The Event that was passed, now modified by listeners.
*/
public function dispatch(object $event);
}
<?php
declare(strict_types=1);
namespace Psr\EventDispatcher;
/**
* An Event whose processing may be interrupted when the event has been handled.
*
* A Dispatcher implementation MUST check to determine if an Event
* is marked as stopped after each listener is called. If it is then it should
* return immediately without calling any further Listeners.
*/
interface StoppableEventInterface
{
/**
* Is propagation stopped?
*
* This will typically only be used by the Dispatcher to determine if the
* previous listener halted propagation.
*
* @return bool
* True if the Event is complete and no further listeners should be called.
* False to continue calling listeners.
*/
public function isPropagationStopped() : bool;
}
<?php
declare(strict_types=1);
namespace Psr\EventDispatcher;
/**
* Mapper from an event to the listeners that are applicable to that event.
*/
interface ListenerProviderInterface
{
/**
* @param object $event
* An event for which to return the relevant listeners.
* @return iterable[callable]
* An iterable (array, iterator, or generator) of callables. Each
* callable MUST be type-compatible with $event.
*/
public function getListenersForEvent(object $event) : iterable;
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Util\Exception;
/**
* Exception class for when XML cannot be parsed properly.
*
* @author Ole Rößner <ole@roessner.it>
*/
class XmlParsingException extends \InvalidArgumentException
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Util\Exception;
/**
* Exception class for when XML parsing with an XSD schema file path or a callable validator produces errors unrelated
* to the actual XML parsing.
*
* @author Ole Rößner <ole@roessner.it>
*/
class InvalidXmlException extends XmlParsingException
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Util;
use Symfony\Component\Config\Util\Exception\InvalidXmlException;
use Symfony\Component\Config\Util\Exception\XmlParsingException;
/**
* XMLUtils is a bunch of utility methods to XML operations.
*
* This class contains static methods only and is not meant to be instantiated.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Martin Hasoň <martin.hason@gmail.com>
* @author Ole Rößner <ole@roessner.it>
*/
class XmlUtils
{
/**
* This class should not be instantiated.
*/
private function __construct()
{
}
/**
* Parses an XML string.
*
* @param string $content An XML string
* @param string|callable|null $schemaOrCallable An XSD schema file path, a callable, or null to disable validation
*
* @return \DOMDocument
*
* @throws XmlParsingException When parsing of XML file returns error
* @throws InvalidXmlException When parsing of XML with schema or callable produces any errors unrelated to the XML parsing itself
* @throws \RuntimeException When DOM extension is missing
*/
public static function parse(string $content, $schemaOrCallable = null)
{
if (!\extension_loaded('dom')) {
throw new \LogicException('Extension DOM is required.');
}
$internalErrors = libxml_use_internal_errors(true);
if (\LIBXML_VERSION < 20900) {
$disableEntities = libxml_disable_entity_loader(true);
}
libxml_clear_errors();
$dom = new \DOMDocument();
$dom->validateOnParse = true;
if (!$dom->loadXML($content, \LIBXML_NONET | (\defined('LIBXML_COMPACT') ? \LIBXML_COMPACT : 0))) {
if (\LIBXML_VERSION < 20900) {
libxml_disable_entity_loader($disableEntities);
}
throw new XmlParsingException(implode("\n", static::getXmlErrors($internalErrors)));
}
$dom->normalizeDocument();
libxml_use_internal_errors($internalErrors);
if (\LIBXML_VERSION < 20900) {
libxml_disable_entity_loader($disableEntities);
}
foreach ($dom->childNodes as $child) {
if (\XML_DOCUMENT_TYPE_NODE === $child->nodeType) {
throw new XmlParsingException('Document types are not allowed.');
}
}
if (null !== $schemaOrCallable) {
$internalErrors = libxml_use_internal_errors(true);
libxml_clear_errors();
$e = null;
if (\is_callable($schemaOrCallable)) {
try {
$valid = $schemaOrCallable($dom, $internalErrors);
} catch (\Exception $e) {
$valid = false;
}
} elseif (!\is_array($schemaOrCallable) && is_file((string) $schemaOrCallable)) {
$schemaSource = file_get_contents((string) $schemaOrCallable);
$valid = @$dom->schemaValidateSource($schemaSource);
} else {
libxml_use_internal_errors($internalErrors);
throw new XmlParsingException('The schemaOrCallable argument has to be a valid path to XSD file or callable.');
}
if (!$valid) {
$messages = static::getXmlErrors($internalErrors);
if (empty($messages)) {
throw new InvalidXmlException('The XML is not valid.', 0, $e);
}
throw new XmlParsingException(implode("\n", $messages), 0, $e);
}
}
libxml_clear_errors();
libxml_use_internal_errors($internalErrors);
return $dom;
}
/**
* Loads an XML file.
*
* @param string $file An XML file path
* @param string|callable|null $schemaOrCallable An XSD schema file path, a callable, or null to disable validation
*
* @return \DOMDocument
*
* @throws \InvalidArgumentException When loading of XML file returns error
* @throws XmlParsingException When XML parsing returns any errors
* @throws \RuntimeException When DOM extension is missing
*/
public static function loadFile(string $file, $schemaOrCallable = null)
{
if (!is_file($file)) {
throw new \InvalidArgumentException(sprintf('Resource "%s" is not a file.', $file));
}
if (!is_readable($file)) {
throw new \InvalidArgumentException(sprintf('File "%s" is not readable.', $file));
}
$content = @file_get_contents($file);
if ('' === trim($content)) {
throw new \InvalidArgumentException(sprintf('File "%s" does not contain valid XML, it is empty.', $file));
}
try {
return static::parse($content, $schemaOrCallable);
} catch (InvalidXmlException $e) {
throw new XmlParsingException(sprintf('The XML file "%s" is not valid.', $file), 0, $e->getPrevious());
}
}
/**
* Converts a \DOMElement object to a PHP array.
*
* The following rules applies during the conversion:
*
* * Each tag is converted to a key value or an array
* if there is more than one "value"
*
* * The content of a tag is set under a "value" key (<foo>bar</foo>)
* if the tag also has some nested tags
*
* * The attributes are converted to keys (<foo foo="bar"/>)
*
* * The nested-tags are converted to keys (<foo><foo>bar</foo></foo>)
*
* @param \DOMElement $element A \DOMElement instance
* @param bool $checkPrefix Check prefix in an element or an attribute name
*
* @return mixed
*/
public static function convertDomElementToArray(\DOMElement $element, bool $checkPrefix = true)
{
$prefix = (string) $element->prefix;
$empty = true;
$config = [];
foreach ($element->attributes as $name => $node) {
if ($checkPrefix && !\in_array((string) $node->prefix, ['', $prefix], true)) {
continue;
}
$config[$name] = static::phpize($node->value);
$empty = false;
}
$nodeValue = false;
foreach ($element->childNodes as $node) {
if ($node instanceof \DOMText) {
if ('' !== trim($node->nodeValue)) {
$nodeValue = trim($node->nodeValue);
$empty = false;
}
} elseif ($checkPrefix && $prefix != (string) $node->prefix) {
continue;
} elseif (!$node instanceof \DOMComment) {
$value = static::convertDomElementToArray($node, $checkPrefix);
$key = $node->localName;
if (isset($config[$key])) {
if (!\is_array($config[$key]) || !\is_int(key($config[$key]))) {
$config[$key] = [$config[$key]];
}
$config[$key][] = $value;
} else {
$config[$key] = $value;
}
$empty = false;
}
}
if (false !== $nodeValue) {
$value = static::phpize($nodeValue);
if (\count($config)) {
$config['value'] = $value;
} else {
$config = $value;
}
}
return !$empty ? $config : null;
}
/**
* Converts an xml value to a PHP type.
*
* @param mixed $value
*
* @return mixed
*/
public static function phpize($value)
{
$value = (string) $value;
$lowercaseValue = strtolower($value);
switch (true) {
case 'null' === $lowercaseValue:
return null;
case ctype_digit($value):
$raw = $value;
$cast = (int) $value;
return '0' == $value[0] ? octdec($value) : (((string) $raw === (string) $cast) ? $cast : $raw);
case isset($value[1]) && '-' === $value[0] && ctype_digit(substr($value, 1)):
$raw = $value;
$cast = (int) $value;
return '0' == $value[1] ? octdec($value) : (((string) $raw === (string) $cast) ? $cast : $raw);
case 'true' === $lowercaseValue:
return true;
case 'false' === $lowercaseValue:
return false;
case isset($value[1]) && '0b' == $value[0].$value[1] && preg_match('/^0b[01]*$/', $value):
return bindec($value);
case is_numeric($value):
return '0x' === $value[0].$value[1] ? hexdec($value) : (float) $value;
case preg_match('/^0x[0-9a-f]++$/i', $value):
return hexdec($value);
case preg_match('/^[+-]?[0-9]+(\.[0-9]+)?$/', $value):
return (float) $value;
default:
return $value;
}
}
protected static function getXmlErrors(bool $internalErrors)
{
$errors = [];
foreach (libxml_get_errors() as $error) {
$errors[] = sprintf('[%s %s] %s (in %s - line %d, column %d)',
\LIBXML_ERR_WARNING == $error->level ? 'WARNING' : 'ERROR',
$error->code,
trim($error->message),
$error->file ?: 'n/a',
$error->line,
$error->column
);
}
libxml_clear_errors();
libxml_use_internal_errors($internalErrors);
return $errors;
}
}
Copyright (c) 2004-2020 Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Exception;
/**
* File locator exception if a file does not exist.
*
* @author Leo Feyer <https://github.com/leofeyer>
*/
class FileLocatorFileNotFoundException extends \InvalidArgumentException
{
private $paths;
public function __construct(string $message = '', int $code = 0, \Throwable $previous = null, array $paths = [])
{
parent::__construct($message, $code, $previous);
$this->paths = $paths;
}
public function getPaths()
{
return $this->paths;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Exception;
/**
* Exception class for when a circular reference is detected when importing resources.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class FileLoaderImportCircularReferenceException extends LoaderLoadException
{
public function __construct(array $resources, int $code = null, \Throwable $previous = null)
{
$message = sprintf('Circular reference detected in "%s" ("%s" > "%s").', $this->varToString($resources[0]), implode('" > "', $resources), $resources[0]);
\Exception::__construct($message, $code, $previous);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Exception;
/**
* Exception class for when a resource cannot be loaded or imported.
*
* @author Ryan Weaver <ryan@thatsquality.com>
*/
class LoaderLoadException extends \Exception
{
/**
* @param string $resource The resource that could not be imported
* @param string $sourceResource The original resource importing the new resource
* @param int $code The error code
* @param \Throwable $previous A previous exception
* @param string $type The type of resource
*/
public function __construct(string $resource, string $sourceResource = null, int $code = null, \Throwable $previous = null, string $type = null)
{
$message = '';
if ($previous) {
// Include the previous exception, to help the user see what might be the underlying cause
// Trim the trailing period of the previous message. We only want 1 period remove so no rtrim...
if ('.' === substr($previous->getMessage(), -1)) {
$trimmedMessage = substr($previous->getMessage(), 0, -1);
$message .= sprintf('%s', $trimmedMessage).' in ';
} else {
$message .= sprintf('%s', $previous->getMessage()).' in ';
}
$message .= $resource.' ';
// show tweaked trace to complete the human readable sentence
if (null === $sourceResource) {
$message .= sprintf('(which is loaded in resource "%s")', $resource);
} else {
$message .= sprintf('(which is being imported from "%s")', $sourceResource);
}
$message .= '.';
// if there's no previous message, present it the default way
} elseif (null === $sourceResource) {
$message .= sprintf('Cannot load resource "%s".', $resource);
} else {
$message .= sprintf('Cannot import resource "%s" from "%s".', $resource, $sourceResource);
}
// Is the resource located inside a bundle?
if ('@' === $resource[0]) {
$parts = explode(\DIRECTORY_SEPARATOR, $resource);
$bundle = substr($parts[0], 1);
$message .= sprintf(' Make sure the "%s" bundle is correctly registered and loaded in the application kernel class.', $bundle);
$message .= sprintf(' If the bundle is registered, make sure the bundle path "%s" is not empty.', $resource);
} elseif (null !== $type) {
// maybe there is no loader for this specific type
if ('annotation' === $type) {
$message .= ' Make sure annotations are installed and enabled.';
} else {
$message .= sprintf(' Make sure there is a loader supporting the "%s" type.', $type);
}
}
parent::__construct($message, $code, $previous);
}
protected function varToString($var)
{
if (\is_object($var)) {
return sprintf('Object(%s)', \get_class($var));
}
if (\is_array($var)) {
$a = [];
foreach ($var as $k => $v) {
$a[] = sprintf('%s => %s', $k, $this->varToString($v));
}
return sprintf('Array(%s)', implode(', ', $a));
}
if (\is_resource($var)) {
return sprintf('Resource(%s)', get_resource_type($var));
}
if (null === $var) {
return 'null';
}
if (false === $var) {
return 'false';
}
if (true === $var) {
return 'true';
}
return (string) $var;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config;
use Symfony\Component\Config\Exception\FileLocatorFileNotFoundException;
/**
* FileLocator uses an array of pre-defined paths to find files.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class FileLocator implements FileLocatorInterface
{
protected $paths;
/**
* @param string|string[] $paths A path or an array of paths where to look for resources
*/
public function __construct($paths = [])
{
$this->paths = (array) $paths;
}
/**
* {@inheritdoc}
*/
public function locate(string $name, string $currentPath = null, bool $first = true)
{
if ('' === $name) {
throw new \InvalidArgumentException('An empty file name is not valid to be located.');
}
if ($this->isAbsolutePath($name)) {
if (!file_exists($name)) {
throw new FileLocatorFileNotFoundException(sprintf('The file "%s" does not exist.', $name), 0, null, [$name]);
}
return $name;
}
$paths = $this->paths;
if (null !== $currentPath) {
array_unshift($paths, $currentPath);
}
$paths = array_unique($paths);
$filepaths = $notfound = [];
foreach ($paths as $path) {
if (@file_exists($file = $path.\DIRECTORY_SEPARATOR.$name)) {
if (true === $first) {
return $file;
}
$filepaths[] = $file;
} else {
$notfound[] = $file;
}
}
if (!$filepaths) {
throw new FileLocatorFileNotFoundException(sprintf('The file "%s" does not exist (in: "%s").', $name, implode('", "', $paths)), 0, null, $notfound);
}
return $filepaths;
}
/**
* Returns whether the file path is an absolute path.
*/
private function isAbsolutePath(string $file): bool
{
if ('/' === $file[0] || '\\' === $file[0]
|| (\strlen($file) > 3 && ctype_alpha($file[0])
&& ':' === $file[1]
&& ('\\' === $file[2] || '/' === $file[2])
)
|| null !== parse_url($file, \PHP_URL_SCHEME)
) {
return true;
}
return false;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition\Builder;
use Symfony\Component\Config\Definition\EnumNode;
/**
* Enum Node Definition.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class EnumNodeDefinition extends ScalarNodeDefinition
{
private $values;
/**
* @return $this
*/
public function values(array $values)
{
$values = array_unique($values);
if (empty($values)) {
throw new \InvalidArgumentException('->values() must be called with at least one value.');
}
$this->values = $values;
return $this;
}
/**
* Instantiate a Node.
*
* @return EnumNode The node
*
* @throws \RuntimeException
*/
protected function instantiateNode()
{
if (null === $this->values) {
throw new \RuntimeException('You must call ->values() on enum nodes.');
}
return new EnumNode($this->name, $this->parent, $this->values, $this->pathSeparator);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition\Builder;
use Symfony\Component\Config\Definition\BooleanNode;
use Symfony\Component\Config\Definition\Exception\InvalidDefinitionException;
/**
* This class provides a fluent interface for defining a node.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class BooleanNodeDefinition extends ScalarNodeDefinition
{
/**
* {@inheritdoc}
*/
public function __construct(?string $name, NodeParentInterface $parent = null)
{
parent::__construct($name, $parent);
$this->nullEquivalent = true;
}
/**
* Instantiate a Node.
*
* @return BooleanNode The node
*/
protected function instantiateNode()
{
return new BooleanNode($this->name, $this->parent, $this->pathSeparator);
}
/**
* {@inheritdoc}
*
* @throws InvalidDefinitionException
*/
public function cannotBeEmpty()
{
throw new InvalidDefinitionException('->cannotBeEmpty() is not applicable to BooleanNodeDefinition.');
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition\Builder;
use Symfony\Component\Config\Definition\BaseNode;
use Symfony\Component\Config\Definition\Exception\InvalidDefinitionException;
use Symfony\Component\Config\Definition\NodeInterface;
/**
* This class provides a fluent interface for defining a node.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
abstract class NodeDefinition implements NodeParentInterface
{
protected $name;
protected $normalization;
protected $validation;
protected $defaultValue;
protected $default = false;
protected $required = false;
protected $deprecation = [];
protected $merge;
protected $allowEmptyValue = true;
protected $nullEquivalent;
protected $trueEquivalent = true;
protected $falseEquivalent = false;
protected $pathSeparator = BaseNode::DEFAULT_PATH_SEPARATOR;
protected $parent;
protected $attributes = [];
public function __construct(?string $name, NodeParentInterface $parent = null)
{
$this->parent = $parent;
$this->name = $name;
}
/**
* Sets the parent node.
*
* @return $this
*/
public function setParent(NodeParentInterface $parent)
{
$this->parent = $parent;
return $this;
}
/**
* Sets info message.
*
* @return $this
*/
public function info(string $info)
{
return $this->attribute('info', $info);
}
/**
* Sets example configuration.
*
* @param string|array $example
*
* @return $this
*/
public function example($example)
{
return $this->attribute('example', $example);
}
/**
* Sets an attribute on the node.
*
* @param mixed $value
*
* @return $this
*/
public function attribute(string $key, $value)
{
$this->attributes[$key] = $value;
return $this;
}
/**
* Returns the parent node.
*
* @return NodeParentInterface|NodeBuilder|NodeDefinition|ArrayNodeDefinition|VariableNodeDefinition|null The builder of the parent node
*/
public function end()
{
return $this->parent;
}
/**
* Creates the node.
*
* @param bool $forceRootNode Whether to force this node as the root node
*
* @return NodeInterface
*/
public function getNode(bool $forceRootNode = false)
{
if ($forceRootNode) {
$this->parent = null;
}
if (null !== $this->normalization) {
$this->normalization->before = ExprBuilder::buildExpressions($this->normalization->before);
}
if (null !== $this->validation) {
$this->validation->rules = ExprBuilder::buildExpressions($this->validation->rules);
}
$node = $this->createNode();
$node->setAttributes($this->attributes);
return $node;
}
/**
* Sets the default value.
*
* @param mixed $value The default value
*
* @return $this
*/
public function defaultValue($value)
{
$this->default = true;
$this->defaultValue = $value;
return $this;
}
/**
* Sets the node as required.
*
* @return $this
*/
public function isRequired()
{
$this->required = true;
return $this;
}
/**
* Sets the node as deprecated.
*
* @param string $package The name of the composer package that is triggering the deprecation
* @param string $version The version of the package that introduced the deprecation
* @param string $message the deprecation message to use
*
* You can use %node% and %path% placeholders in your message to display,
* respectively, the node name and its complete path
*
* @return $this
*/
public function setDeprecated(/* string $package, string $version, string $message = 'The child node "%node%" at path "%path%" is deprecated.' */)
{
$args = \func_get_args();
if (\func_num_args() < 2) {
trigger_deprecation('symfony/config', '5.1', 'The signature of method "%s()" requires 3 arguments: "string $package, string $version, string $message", not defining them is deprecated.', __METHOD__);
$message = $args[0] ?? 'The child node "%node%" at path "%path%" is deprecated.';
$package = $version = '';
} else {
$package = (string) $args[0];
$version = (string) $args[1];
$message = (string) ($args[2] ?? 'The child node "%node%" at path "%path%" is deprecated.');
}
$this->deprecation = [
'package' => $package,
'version' => $version,
'message' => $message,
];
return $this;
}
/**
* Sets the equivalent value used when the node contains null.
*
* @param mixed $value
*
* @return $this
*/
public function treatNullLike($value)
{
$this->nullEquivalent = $value;
return $this;
}
/**
* Sets the equivalent value used when the node contains true.
*
* @param mixed $value
*
* @return $this
*/
public function treatTrueLike($value)
{
$this->trueEquivalent = $value;
return $this;
}
/**
* Sets the equivalent value used when the node contains false.
*
* @param mixed $value
*
* @return $this
*/
public function treatFalseLike($value)
{
$this->falseEquivalent = $value;
return $this;
}
/**
* Sets null as the default value.
*
* @return $this
*/
public function defaultNull()
{
return $this->defaultValue(null);
}
/**
* Sets true as the default value.
*
* @return $this
*/
public function defaultTrue()
{
return $this->defaultValue(true);
}
/**
* Sets false as the default value.
*
* @return $this
*/
public function defaultFalse()
{
return $this->defaultValue(false);
}
/**
* Sets an expression to run before the normalization.
*
* @return ExprBuilder
*/
public function beforeNormalization()
{
return $this->normalization()->before();
}
/**
* Denies the node value being empty.
*
* @return $this
*/
public function cannotBeEmpty()
{
$this->allowEmptyValue = false;
return $this;
}
/**
* Sets an expression to run for the validation.
*
* The expression receives the value of the node and must return it. It can
* modify it.
* An exception should be thrown when the node is not valid.
*
* @return ExprBuilder
*/
public function validate()
{
return $this->validation()->rule();
}
/**
* Sets whether the node can be overwritten.
*
* @return $this
*/
public function cannotBeOverwritten(bool $deny = true)
{
$this->merge()->denyOverwrite($deny);
return $this;
}
/**
* Gets the builder for validation rules.
*
* @return ValidationBuilder
*/
protected function validation()
{
if (null === $this->validation) {
$this->validation = new ValidationBuilder($this);
}
return $this->validation;
}
/**
* Gets the builder for merging rules.
*
* @return MergeBuilder
*/
protected function merge()
{
if (null === $this->merge) {
$this->merge = new MergeBuilder($this);
}
return $this->merge;
}
/**
* Gets the builder for normalization rules.
*
* @return NormalizationBuilder
*/
protected function normalization()
{
if (null === $this->normalization) {
$this->normalization = new NormalizationBuilder($this);
}
return $this->normalization;
}
/**
* Instantiate and configure the node according to this definition.
*
* @return NodeInterface The node instance
*
* @throws InvalidDefinitionException When the definition is invalid
*/
abstract protected function createNode();
/**
* Set PathSeparator to use.
*
* @return $this
*/
public function setPathSeparator(string $separator)
{
if ($this instanceof ParentNodeDefinitionInterface) {
foreach ($this->getChildNodeDefinitions() as $child) {
$child->setPathSeparator($separator);
}
}
$this->pathSeparator = $separator;
return $this;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition\Builder;
use Symfony\Component\Config\Definition\NodeInterface;
/**
* This is the entry class for building a config tree.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class TreeBuilder implements NodeParentInterface
{
protected $tree;
protected $root;
public function __construct(string $name, string $type = 'array', NodeBuilder $builder = null)
{
$builder = $builder ?: new NodeBuilder();
$this->root = $builder->node($name, $type)->setParent($this);
}
/**
* @return NodeDefinition|ArrayNodeDefinition The root node (as an ArrayNodeDefinition when the type is 'array')
*/
public function getRootNode(): NodeDefinition
{
return $this->root;
}
/**
* Builds the tree.
*
* @return NodeInterface
*
* @throws \RuntimeException
*/
public function buildTree()
{
if (null !== $this->tree) {
return $this->tree;
}
return $this->tree = $this->root->getNode(true);
}
public function setPathSeparator(string $separator)
{
// unset last built as changing path separator changes all nodes
$this->tree = null;
$this->root->setPathSeparator($separator);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition\Builder;
/**
* An interface that can be implemented by nodes which build other nodes.
*
* @author Roland Franssen <franssen.roland@gmail.com>
*/
interface BuilderAwareInterface
{
/**
* Sets a custom children builder.
*/
public function setBuilder(NodeBuilder $builder);
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition\Builder;
use Symfony\Component\Config\Definition\FloatNode;
/**
* This class provides a fluent interface for defining a float node.
*
* @author Jeanmonod David <david.jeanmonod@gmail.com>
*/
class FloatNodeDefinition extends NumericNodeDefinition
{
/**
* Instantiates a Node.
*
* @return FloatNode The node
*/
protected function instantiateNode()
{
return new FloatNode($this->name, $this->parent, $this->min, $this->max, $this->pathSeparator);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition\Builder;
use Symfony\Component\Config\Definition\IntegerNode;
/**
* This class provides a fluent interface for defining an integer node.
*
* @author Jeanmonod David <david.jeanmonod@gmail.com>
*/
class IntegerNodeDefinition extends NumericNodeDefinition
{
/**
* Instantiates a Node.
*
* @return IntegerNode The node
*/
protected function instantiateNode()
{
return new IntegerNode($this->name, $this->parent, $this->min, $this->max, $this->pathSeparator);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition\Builder;
/**
* An interface that must be implemented by all node parents.
*
* @author Victor Berchet <victor@suumit.com>
*/
interface NodeParentInterface
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition\Builder;
use Symfony\Component\Config\Definition\ScalarNode;
/**
* This class provides a fluent interface for defining a node.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class ScalarNodeDefinition extends VariableNodeDefinition
{
/**
* Instantiate a Node.
*
* @return ScalarNode The node
*/
protected function instantiateNode()
{
return new ScalarNode($this->name, $this->parent, $this->pathSeparator);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition\Builder;
use Symfony\Component\Config\Definition\Exception\InvalidDefinitionException;
/**
* Abstract class that contains common code of integer and float node definitions.
*
* @author David Jeanmonod <david.jeanmonod@gmail.com>
*/
abstract class NumericNodeDefinition extends ScalarNodeDefinition
{
protected $min;
protected $max;
/**
* Ensures that the value is smaller than the given reference.
*
* @param mixed $max
*
* @return $this
*
* @throws \InvalidArgumentException when the constraint is inconsistent
*/
public function max($max)
{
if (isset($this->min) && $this->min > $max) {
throw new \InvalidArgumentException(sprintf('You cannot define a max(%s) as you already have a min(%s).', $max, $this->min));
}
$this->max = $max;
return $this;
}
/**
* Ensures that the value is bigger than the given reference.
*
* @param mixed $min
*
* @return $this
*
* @throws \InvalidArgumentException when the constraint is inconsistent
*/
public function min($min)
{
if (isset($this->max) && $this->max < $min) {
throw new \InvalidArgumentException(sprintf('You cannot define a min(%s) as you already have a max(%s).', $min, $this->max));
}
$this->min = $min;
return $this;
}
/**
* {@inheritdoc}
*
* @throws InvalidDefinitionException
*/
public function cannotBeEmpty()
{
throw new InvalidDefinitionException('->cannotBeEmpty() is not applicable to NumericNodeDefinition.');
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition\Builder;
use Symfony\Component\Config\Definition\ArrayNode;
use Symfony\Component\Config\Definition\Exception\InvalidDefinitionException;
use Symfony\Component\Config\Definition\PrototypedArrayNode;
/**
* This class provides a fluent interface for defining an array node.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class ArrayNodeDefinition extends NodeDefinition implements ParentNodeDefinitionInterface
{
protected $performDeepMerging = true;
protected $ignoreExtraKeys = false;
protected $removeExtraKeys = true;
protected $children = [];
protected $prototype;
protected $atLeastOne = false;
protected $allowNewKeys = true;
protected $key;
protected $removeKeyItem;
protected $addDefaults = false;
protected $addDefaultChildren = false;
protected $nodeBuilder;
protected $normalizeKeys = true;
/**
* {@inheritdoc}
*/
public function __construct(?string $name, NodeParentInterface $parent = null)
{
parent::__construct($name, $parent);
$this->nullEquivalent = [];
$this->trueEquivalent = [];
}
/**
* {@inheritdoc}
*/
public function setBuilder(NodeBuilder $builder)
{
$this->nodeBuilder = $builder;
}
/**
* {@inheritdoc}
*/
public function children()
{
return $this->getNodeBuilder();
}
/**
* Sets a prototype for child nodes.
*
* @return NodeDefinition
*/
public function prototype(string $type)
{
return $this->prototype = $this->getNodeBuilder()->node(null, $type)->setParent($this);
}
/**
* @return VariableNodeDefinition
*/
public function variablePrototype()
{
return $this->prototype('variable');
}
/**
* @return ScalarNodeDefinition
*/
public function scalarPrototype()
{
return $this->prototype('scalar');
}
/**
* @return BooleanNodeDefinition
*/
public function booleanPrototype()
{
return $this->prototype('boolean');
}
/**
* @return IntegerNodeDefinition
*/
public function integerPrototype()
{
return $this->prototype('integer');
}
/**
* @return FloatNodeDefinition
*/
public function floatPrototype()
{
return $this->prototype('float');
}
/**
* @return ArrayNodeDefinition
*/
public function arrayPrototype()
{
return $this->prototype('array');
}
/**
* @return EnumNodeDefinition
*/
public function enumPrototype()
{
return $this->prototype('enum');
}
/**
* Adds the default value if the node is not set in the configuration.
*
* This method is applicable to concrete nodes only (not to prototype nodes).
* If this function has been called and the node is not set during the finalization
* phase, it's default value will be derived from its children default values.
*
* @return $this
*/
public function addDefaultsIfNotSet()
{
$this->addDefaults = true;
return $this;
}
/**
* Adds children with a default value when none are defined.
*
* This method is applicable to prototype nodes only.
*
* @param int|string|array|null $children The number of children|The child name|The children names to be added
*
* @return $this
*/
public function addDefaultChildrenIfNoneSet($children = null)
{
$this->addDefaultChildren = $children;
return $this;
}
/**
* Requires the node to have at least one element.
*
* This method is applicable to prototype nodes only.
*
* @return $this
*/
public function requiresAtLeastOneElement()
{
$this->atLeastOne = true;
return $this;
}
/**
* Disallows adding news keys in a subsequent configuration.
*
* If used all keys have to be defined in the same configuration file.
*
* @return $this
*/
public function disallowNewKeysInSubsequentConfigs()
{
$this->allowNewKeys = false;
return $this;
}
/**
* Sets a normalization rule for XML configurations.
*
* @param string $singular The key to remap
* @param string|null $plural The plural of the key for irregular plurals
*
* @return $this
*/
public function fixXmlConfig(string $singular, string $plural = null)
{
$this->normalization()->remap($singular, $plural);
return $this;
}
/**
* Sets the attribute which value is to be used as key.
*
* This is useful when you have an indexed array that should be an
* associative array. You can select an item from within the array
* to be the key of the particular item. For example, if "id" is the
* "key", then:
*
* [
* ['id' => 'my_name', 'foo' => 'bar'],
* ];
*
* becomes
*
* [
* 'my_name' => ['foo' => 'bar'],
* ];
*
* If you'd like "'id' => 'my_name'" to still be present in the resulting
* array, then you can set the second argument of this method to false.
*
* This method is applicable to prototype nodes only.
*
* @param string $name The name of the key
* @param bool $removeKeyItem Whether or not the key item should be removed
*
* @return $this
*/
public function useAttributeAsKey(string $name, bool $removeKeyItem = true)
{
$this->key = $name;
$this->removeKeyItem = $removeKeyItem;
return $this;
}
/**
* Sets whether the node can be unset.
*
* @return $this
*/
public function canBeUnset(bool $allow = true)
{
$this->merge()->allowUnset($allow);
return $this;
}
/**
* Adds an "enabled" boolean to enable the current section.
*
* By default, the section is disabled. If any configuration is specified then
* the node will be automatically enabled:
*
* enableableArrayNode: {enabled: true, ...} # The config is enabled & default values get overridden
* enableableArrayNode: ~ # The config is enabled & use the default values
* enableableArrayNode: true # The config is enabled & use the default values
* enableableArrayNode: {other: value, ...} # The config is enabled & default values get overridden
* enableableArrayNode: {enabled: false, ...} # The config is disabled
* enableableArrayNode: false # The config is disabled
*
* @return $this
*/
public function canBeEnabled()
{
$this
->addDefaultsIfNotSet()
->treatFalseLike(['enabled' => false])
->treatTrueLike(['enabled' => true])
->treatNullLike(['enabled' => true])
->beforeNormalization()
->ifArray()
->then(function ($v) {
$v['enabled'] = isset($v['enabled']) ? $v['enabled'] : true;
return $v;
})
->end()
->children()
->booleanNode('enabled')
->defaultFalse()
;
return $this;
}
/**
* Adds an "enabled" boolean to enable the current section.
*
* By default, the section is enabled.
*
* @return $this
*/
public function canBeDisabled()
{
$this
->addDefaultsIfNotSet()
->treatFalseLike(['enabled' => false])
->treatTrueLike(['enabled' => true])
->treatNullLike(['enabled' => true])
->children()
->booleanNode('enabled')
->defaultTrue()
;
return $this;
}
/**
* Disables the deep merging of the node.
*
* @return $this
*/
public function performNoDeepMerging()
{
$this->performDeepMerging = false;
return $this;
}
/**
* Allows extra config keys to be specified under an array without
* throwing an exception.
*
* Those config values are ignored and removed from the resulting
* array. This should be used only in special cases where you want
* to send an entire configuration array through a special tree that
* processes only part of the array.
*
* @param bool $remove Whether to remove the extra keys
*
* @return $this
*/
public function ignoreExtraKeys(bool $remove = true)
{
$this->ignoreExtraKeys = true;
$this->removeExtraKeys = $remove;
return $this;
}
/**
* Sets whether to enable key normalization.
*
* @return $this
*/
public function normalizeKeys(bool $bool)
{
$this->normalizeKeys = $bool;
return $this;
}
/**
* {@inheritdoc}
*/
public function append(NodeDefinition $node)
{
$this->children[$node->name] = $node->setParent($this);
return $this;
}
/**
* Returns a node builder to be used to add children and prototype.
*
* @return NodeBuilder The node builder
*/
protected function getNodeBuilder()
{
if (null === $this->nodeBuilder) {
$this->nodeBuilder = new NodeBuilder();
}
return $this->nodeBuilder->setParent($this);
}
/**
* {@inheritdoc}
*/
protected function createNode()
{
if (null === $this->prototype) {
$node = new ArrayNode($this->name, $this->parent, $this->pathSeparator);
$this->validateConcreteNode($node);
$node->setAddIfNotSet($this->addDefaults);
foreach ($this->children as $child) {
$child->parent = $node;
$node->addChild($child->getNode());
}
} else {
$node = new PrototypedArrayNode($this->name, $this->parent, $this->pathSeparator);
$this->validatePrototypeNode($node);
if (null !== $this->key) {
$node->setKeyAttribute($this->key, $this->removeKeyItem);
}
if (true === $this->atLeastOne || false === $this->allowEmptyValue) {
$node->setMinNumberOfElements(1);
}
if ($this->default) {
if (!\is_array($this->defaultValue)) {
throw new \InvalidArgumentException(sprintf('%s: the default value of an array node has to be an array.', $node->getPath()));
}
$node->setDefaultValue($this->defaultValue);
}
if (false !== $this->addDefaultChildren) {
$node->setAddChildrenIfNoneSet($this->addDefaultChildren);
if ($this->prototype instanceof static && null === $this->prototype->prototype) {
$this->prototype->addDefaultsIfNotSet();
}
}
$this->prototype->parent = $node;
$node->setPrototype($this->prototype->getNode());
}
$node->setAllowNewKeys($this->allowNewKeys);
$node->addEquivalentValue(null, $this->nullEquivalent);
$node->addEquivalentValue(true, $this->trueEquivalent);
$node->addEquivalentValue(false, $this->falseEquivalent);
$node->setPerformDeepMerging($this->performDeepMerging);
$node->setRequired($this->required);
$node->setIgnoreExtraKeys($this->ignoreExtraKeys, $this->removeExtraKeys);
$node->setNormalizeKeys($this->normalizeKeys);
if ($this->deprecation) {
$node->setDeprecated($this->deprecation['package'], $this->deprecation['version'], $this->deprecation['message']);
}
if (null !== $this->normalization) {
$node->setNormalizationClosures($this->normalization->before);
$node->setXmlRemappings($this->normalization->remappings);
}
if (null !== $this->merge) {
$node->setAllowOverwrite($this->merge->allowOverwrite);
$node->setAllowFalse($this->merge->allowFalse);
}
if (null !== $this->validation) {
$node->setFinalValidationClosures($this->validation->rules);
}
return $node;
}
/**
* Validate the configuration of a concrete node.
*
* @throws InvalidDefinitionException
*/
protected function validateConcreteNode(ArrayNode $node)
{
$path = $node->getPath();
if (null !== $this->key) {
throw new InvalidDefinitionException(sprintf('->useAttributeAsKey() is not applicable to concrete nodes at path "%s".', $path));
}
if (false === $this->allowEmptyValue) {
throw new InvalidDefinitionException(sprintf('->cannotBeEmpty() is not applicable to concrete nodes at path "%s".', $path));
}
if (true === $this->atLeastOne) {
throw new InvalidDefinitionException(sprintf('->requiresAtLeastOneElement() is not applicable to concrete nodes at path "%s".', $path));
}
if ($this->default) {
throw new InvalidDefinitionException(sprintf('->defaultValue() is not applicable to concrete nodes at path "%s".', $path));
}
if (false !== $this->addDefaultChildren) {
throw new InvalidDefinitionException(sprintf('->addDefaultChildrenIfNoneSet() is not applicable to concrete nodes at path "%s".', $path));
}
}
/**
* Validate the configuration of a prototype node.
*
* @throws InvalidDefinitionException
*/
protected function validatePrototypeNode(PrototypedArrayNode $node)
{
$path = $node->getPath();
if ($this->addDefaults) {
throw new InvalidDefinitionException(sprintf('->addDefaultsIfNotSet() is not applicable to prototype nodes at path "%s".', $path));
}
if (false !== $this->addDefaultChildren) {
if ($this->default) {
throw new InvalidDefinitionException(sprintf('A default value and default children might not be used together at path "%s".', $path));
}
if (null !== $this->key && (null === $this->addDefaultChildren || \is_int($this->addDefaultChildren) && $this->addDefaultChildren > 0)) {
throw new InvalidDefinitionException(sprintf('->addDefaultChildrenIfNoneSet() should set default children names as ->useAttributeAsKey() is used at path "%s".', $path));
}
if (null === $this->key && (\is_string($this->addDefaultChildren) || \is_array($this->addDefaultChildren))) {
throw new InvalidDefinitionException(sprintf('->addDefaultChildrenIfNoneSet() might not set default children names as ->useAttributeAsKey() is not used at path "%s".', $path));
}
}
}
/**
* @return NodeDefinition[]
*/
public function getChildNodeDefinitions()
{
return $this->children;
}
/**
* Finds a node defined by the given $nodePath.
*
* @param string $nodePath The path of the node to find. e.g "doctrine.orm.mappings"
*/
public function find(string $nodePath): NodeDefinition
{
$firstPathSegment = (false === $pathSeparatorPos = strpos($nodePath, $this->pathSeparator))
? $nodePath
: substr($nodePath, 0, $pathSeparatorPos);
if (null === $node = ($this->children[$firstPathSegment] ?? null)) {
throw new \RuntimeException(sprintf('Node with name "%s" does not exist in the current node "%s".', $firstPathSegment, $this->name));
}
if (false === $pathSeparatorPos) {
return $node;
}
return $node->find(substr($nodePath, $pathSeparatorPos + \strlen($this->pathSeparator)));
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition\Builder;
use Symfony\Component\Config\Definition\VariableNode;
/**
* This class provides a fluent interface for defining a node.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class VariableNodeDefinition extends NodeDefinition
{
/**
* Instantiate a Node.
*
* @return VariableNode The node
*/
protected function instantiateNode()
{
return new VariableNode($this->name, $this->parent, $this->pathSeparator);
}
/**
* {@inheritdoc}
*/
protected function createNode()
{
$node = $this->instantiateNode();
if (null !== $this->normalization) {
$node->setNormalizationClosures($this->normalization->before);
}
if (null !== $this->merge) {
$node->setAllowOverwrite($this->merge->allowOverwrite);
}
if (true === $this->default) {
$node->setDefaultValue($this->defaultValue);
}
$node->setAllowEmptyValue($this->allowEmptyValue);
$node->addEquivalentValue(null, $this->nullEquivalent);
$node->addEquivalentValue(true, $this->trueEquivalent);
$node->addEquivalentValue(false, $this->falseEquivalent);
$node->setRequired($this->required);
if ($this->deprecation) {
$node->setDeprecated($this->deprecation['package'], $this->deprecation['version'], $this->deprecation['message']);
}
if (null !== $this->validation) {
$node->setFinalValidationClosures($this->validation->rules);
}
return $node;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition\Builder;
use Symfony\Component\Config\Definition\Exception\UnsetKeyException;
/**
* This class builds an if expression.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
* @author Christophe Coevoet <stof@notk.org>
*/
class ExprBuilder
{
protected $node;
public $ifPart;
public $thenPart;
public function __construct(NodeDefinition $node)
{
$this->node = $node;
}
/**
* Marks the expression as being always used.
*
* @return $this
*/
public function always(\Closure $then = null)
{
$this->ifPart = function ($v) { return true; };
if (null !== $then) {
$this->thenPart = $then;
}
return $this;
}
/**
* Sets a closure to use as tests.
*
* The default one tests if the value is true.
*
* @return $this
*/
public function ifTrue(\Closure $closure = null)
{
if (null === $closure) {
$closure = function ($v) { return true === $v; };
}
$this->ifPart = $closure;
return $this;
}
/**
* Tests if the value is a string.
*
* @return $this
*/
public function ifString()
{
$this->ifPart = function ($v) { return \is_string($v); };
return $this;
}
/**
* Tests if the value is null.
*
* @return $this
*/
public function ifNull()
{
$this->ifPart = function ($v) { return null === $v; };
return $this;
}
/**
* Tests if the value is empty.
*
* @return ExprBuilder
*/
public function ifEmpty()
{
$this->ifPart = function ($v) { return empty($v); };
return $this;
}
/**
* Tests if the value is an array.
*
* @return $this
*/
public function ifArray()
{
$this->ifPart = function ($v) { return \is_array($v); };
return $this;
}
/**
* Tests if the value is in an array.
*
* @return $this
*/
public function ifInArray(array $array)
{
$this->ifPart = function ($v) use ($array) { return \in_array($v, $array, true); };
return $this;
}
/**
* Tests if the value is not in an array.
*
* @return $this
*/
public function ifNotInArray(array $array)
{
$this->ifPart = function ($v) use ($array) { return !\in_array($v, $array, true); };
return $this;
}
/**
* Transforms variables of any type into an array.
*
* @return $this
*/
public function castToArray()
{
$this->ifPart = function ($v) { return !\is_array($v); };
$this->thenPart = function ($v) { return [$v]; };
return $this;
}
/**
* Sets the closure to run if the test pass.
*
* @return $this
*/
public function then(\Closure $closure)
{
$this->thenPart = $closure;
return $this;
}
/**
* Sets a closure returning an empty array.
*
* @return $this
*/
public function thenEmptyArray()
{
$this->thenPart = function ($v) { return []; };
return $this;
}
/**
* Sets a closure marking the value as invalid at processing time.
*
* if you want to add the value of the node in your message just use a %s placeholder.
*
* @return $this
*
* @throws \InvalidArgumentException
*/
public function thenInvalid(string $message)
{
$this->thenPart = function ($v) use ($message) { throw new \InvalidArgumentException(sprintf($message, json_encode($v))); };
return $this;
}
/**
* Sets a closure unsetting this key of the array at processing time.
*
* @return $this
*
* @throws UnsetKeyException
*/
public function thenUnset()
{
$this->thenPart = function ($v) { throw new UnsetKeyException('Unsetting key.'); };
return $this;
}
/**
* Returns the related node.
*
* @return NodeDefinition|ArrayNodeDefinition|VariableNodeDefinition
*
* @throws \RuntimeException
*/
public function end()
{
if (null === $this->ifPart) {
throw new \RuntimeException('You must specify an if part.');
}
if (null === $this->thenPart) {
throw new \RuntimeException('You must specify a then part.');
}
return $this->node;
}
/**
* Builds the expressions.
*
* @param ExprBuilder[] $expressions An array of ExprBuilder instances to build
*
* @return array
*/
public static function buildExpressions(array $expressions)
{
foreach ($expressions as $k => $expr) {
if ($expr instanceof self) {
$if = $expr->ifPart;
$then = $expr->thenPart;
$expressions[$k] = function ($v) use ($if, $then) {
return $if($v) ? $then($v) : $v;
};
}
}
return $expressions;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition\Builder;
/**
* This class provides a fluent interface for building a node.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class NodeBuilder implements NodeParentInterface
{
protected $parent;
protected $nodeMapping;
public function __construct()
{
$this->nodeMapping = [
'variable' => VariableNodeDefinition::class,
'scalar' => ScalarNodeDefinition::class,
'boolean' => BooleanNodeDefinition::class,
'integer' => IntegerNodeDefinition::class,
'float' => FloatNodeDefinition::class,
'array' => ArrayNodeDefinition::class,
'enum' => EnumNodeDefinition::class,
];
}
/**
* Set the parent node.
*
* @return $this
*/
public function setParent(ParentNodeDefinitionInterface $parent = null)
{
$this->parent = $parent;
return $this;
}
/**
* Creates a child array node.
*
* @return ArrayNodeDefinition The child node
*/
public function arrayNode(string $name)
{
return $this->node($name, 'array');
}
/**
* Creates a child scalar node.
*
* @return ScalarNodeDefinition The child node
*/
public function scalarNode(string $name)
{
return $this->node($name, 'scalar');
}
/**
* Creates a child Boolean node.
*
* @return BooleanNodeDefinition The child node
*/
public function booleanNode(string $name)
{
return $this->node($name, 'boolean');
}
/**
* Creates a child integer node.
*
* @return IntegerNodeDefinition The child node
*/
public function integerNode(string $name)
{
return $this->node($name, 'integer');
}
/**
* Creates a child float node.
*
* @return FloatNodeDefinition The child node
*/
public function floatNode(string $name)
{
return $this->node($name, 'float');
}
/**
* Creates a child EnumNode.
*
* @return EnumNodeDefinition
*/
public function enumNode(string $name)
{
return $this->node($name, 'enum');
}
/**
* Creates a child variable node.
*
* @return VariableNodeDefinition The builder of the child node
*/
public function variableNode(string $name)
{
return $this->node($name, 'variable');
}
/**
* Returns the parent node.
*
* @return NodeDefinition&ParentNodeDefinitionInterface The parent node
*/
public function end()
{
return $this->parent;
}
/**
* Creates a child node.
*
* @return NodeDefinition The child node
*
* @throws \RuntimeException When the node type is not registered
* @throws \RuntimeException When the node class is not found
*/
public function node(?string $name, string $type)
{
$class = $this->getNodeClass($type);
$node = new $class($name);
$this->append($node);
return $node;
}
/**
* Appends a node definition.
*
* Usage:
*
* $node = new ArrayNodeDefinition('name')
* ->children()
* ->scalarNode('foo')->end()
* ->scalarNode('baz')->end()
* ->append($this->getBarNodeDefinition())
* ->end()
* ;
*
* @return $this
*/
public function append(NodeDefinition $node)
{
if ($node instanceof BuilderAwareInterface) {
$builder = clone $this;
$builder->setParent(null);
$node->setBuilder($builder);
}
if (null !== $this->parent) {
$this->parent->append($node);
// Make this builder the node parent to allow for a fluid interface
$node->setParent($this);
}
return $this;
}
/**
* Adds or overrides a node Type.
*
* @param string $type The name of the type
* @param string $class The fully qualified name the node definition class
*
* @return $this
*/
public function setNodeClass(string $type, string $class)
{
$this->nodeMapping[strtolower($type)] = $class;
return $this;
}
/**
* Returns the class name of the node definition.
*
* @return string The node definition class name
*
* @throws \RuntimeException When the node type is not registered
* @throws \RuntimeException When the node class is not found
*/
protected function getNodeClass(string $type)
{
$type = strtolower($type);
if (!isset($this->nodeMapping[$type])) {
throw new \RuntimeException(sprintf('The node type "%s" is not registered.', $type));
}
$class = $this->nodeMapping[$type];
if (!class_exists($class)) {
throw new \RuntimeException(sprintf('The node class "%s" does not exist.', $class));
}
return $class;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition\Builder;
/**
* This class builds validation conditions.
*
* @author Christophe Coevoet <stof@notk.org>
*/
class ValidationBuilder
{
protected $node;
public $rules = [];
public function __construct(NodeDefinition $node)
{
$this->node = $node;
}
/**
* Registers a closure to run as normalization or an expression builder to build it if null is provided.
*
* @return ExprBuilder|$this
*/
public function rule(\Closure $closure = null)
{
if (null !== $closure) {
$this->rules[] = $closure;
return $this;
}
return $this->rules[] = new ExprBuilder($this->node);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition\Builder;
/**
* This class builds normalization conditions.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class NormalizationBuilder
{
protected $node;
public $before = [];
public $remappings = [];
public function __construct(NodeDefinition $node)
{
$this->node = $node;
}
/**
* Registers a key to remap to its plural form.
*
* @param string $key The key to remap
* @param string|null $plural The plural of the key in case of irregular plural
*
* @return $this
*/
public function remap(string $key, string $plural = null)
{
$this->remappings[] = [$key, null === $plural ? $key.'s' : $plural];
return $this;
}
/**
* Registers a closure to run before the normalization or an expression builder to build it if null is provided.
*
* @return ExprBuilder|$this
*/
public function before(\Closure $closure = null)
{
if (null !== $closure) {
$this->before[] = $closure;
return $this;
}
return $this->before[] = new ExprBuilder($this->node);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition\Builder;
/**
* This class builds merge conditions.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class MergeBuilder
{
protected $node;
public $allowFalse = false;
public $allowOverwrite = true;
public function __construct(NodeDefinition $node)
{
$this->node = $node;
}
/**
* Sets whether the node can be unset.
*
* @return $this
*/
public function allowUnset(bool $allow = true)
{
$this->allowFalse = $allow;
return $this;
}
/**
* Sets whether the node can be overwritten.
*
* @return $this
*/
public function denyOverwrite(bool $deny = true)
{
$this->allowOverwrite = !$deny;
return $this;
}
/**
* Returns the related node.
*
* @return NodeDefinition|ArrayNodeDefinition|VariableNodeDefinition
*/
public function end()
{
return $this->node;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition\Builder;
/**
* An interface that must be implemented by nodes which can have children.
*
* @author Victor Berchet <victor@suumit.com>
*/
interface ParentNodeDefinitionInterface extends BuilderAwareInterface
{
/**
* Returns a builder to add children nodes.
*
* @return NodeBuilder
*/
public function children();
/**
* Appends a node definition.
*
* Usage:
*
* $node = $parentNode
* ->children()
* ->scalarNode('foo')->end()
* ->scalarNode('baz')->end()
* ->append($this->getBarNodeDefinition())
* ->end()
* ;
*
* @return $this
*/
public function append(NodeDefinition $node);
/**
* Gets the child node definitions.
*
* @return NodeDefinition[]
*/
public function getChildNodeDefinitions();
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition;
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
/**
* This node represents a value of variable type in the config tree.
*
* This node is intended for values of arbitrary type.
* Any PHP type is accepted as a value.
*
* @author Jeremy Mikola <jmikola@gmail.com>
*/
class VariableNode extends BaseNode implements PrototypeNodeInterface
{
protected $defaultValueSet = false;
protected $defaultValue;
protected $allowEmptyValue = true;
public function setDefaultValue($value)
{
$this->defaultValueSet = true;
$this->defaultValue = $value;
}
/**
* {@inheritdoc}
*/
public function hasDefaultValue()
{
return $this->defaultValueSet;
}
/**
* {@inheritdoc}
*/
public function getDefaultValue()
{
$v = $this->defaultValue;
return $v instanceof \Closure ? $v() : $v;
}
/**
* Sets if this node is allowed to have an empty value.
*
* @param bool $boolean True if this entity will accept empty values
*/
public function setAllowEmptyValue(bool $boolean)
{
$this->allowEmptyValue = $boolean;
}
/**
* {@inheritdoc}
*/
public function setName(string $name)
{
$this->name = $name;
}
/**
* {@inheritdoc}
*/
protected function validateType($value)
{
}
/**
* {@inheritdoc}
*/
protected function finalizeValue($value)
{
// deny environment variables only when using custom validators
// this avoids ever passing an empty value to final validation closures
if (!$this->allowEmptyValue && $this->isHandlingPlaceholder() && $this->finalValidationClosures) {
$e = new InvalidConfigurationException(sprintf('The path "%s" cannot contain an environment variable when empty values are not allowed by definition and are validated.', $this->getPath()));
if ($hint = $this->getInfo()) {
$e->addHint($hint);
}
$e->setPath($this->getPath());
throw $e;
}
if (!$this->allowEmptyValue && $this->isValueEmpty($value)) {
$ex = new InvalidConfigurationException(sprintf('The path "%s" cannot contain an empty value, but got %s.', $this->getPath(), json_encode($value)));
if ($hint = $this->getInfo()) {
$ex->addHint($hint);
}
$ex->setPath($this->getPath());
throw $ex;
}
return $value;
}
/**
* {@inheritdoc}
*/
protected function normalizeValue($value)
{
return $value;
}
/**
* {@inheritdoc}
*/
protected function mergeValues($leftSide, $rightSide)
{
return $rightSide;
}
/**
* Evaluates if the given value is to be treated as empty.
*
* By default, PHP's empty() function is used to test for emptiness. This
* method may be overridden by subtypes to better match their understanding
* of empty data.
*
* @param mixed $value
*
* @return bool
*
* @see finalizeValue()
*/
protected function isValueEmpty($value)
{
return empty($value);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition;
/**
* This class is the entry point for config normalization/merging/finalization.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*
* @final
*/
class Processor
{
/**
* Processes an array of configurations.
*
* @param array $configs An array of configuration items to process
*
* @return array The processed configuration
*/
public function process(NodeInterface $configTree, array $configs): array
{
$currentConfig = [];
foreach ($configs as $config) {
$config = $configTree->normalize($config);
$currentConfig = $configTree->merge($currentConfig, $config);
}
return $configTree->finalize($currentConfig);
}
/**
* Processes an array of configurations.
*
* @param array $configs An array of configuration items to process
*
* @return array The processed configuration
*/
public function processConfiguration(ConfigurationInterface $configuration, array $configs): array
{
return $this->process($configuration->getConfigTreeBuilder()->buildTree(), $configs);
}
/**
* Normalizes a configuration entry.
*
* This method returns a normalize configuration array for a given key
* to remove the differences due to the original format (YAML and XML mainly).
*
* Here is an example.
*
* The configuration in XML:
*
* <twig:extension>twig.extension.foo</twig:extension>
* <twig:extension>twig.extension.bar</twig:extension>
*
* And the same configuration in YAML:
*
* extensions: ['twig.extension.foo', 'twig.extension.bar']
*
* @param array $config A config array
* @param string $key The key to normalize
* @param string $plural The plural form of the key if it is irregular
*/
public static function normalizeConfig(array $config, string $key, string $plural = null): array
{
if (null === $plural) {
$plural = $key.'s';
}
if (isset($config[$plural])) {
return $config[$plural];
}
if (isset($config[$key])) {
if (\is_string($config[$key]) || !\is_int(key($config[$key]))) {
// only one
return [$config[$key]];
}
return $config[$key];
}
return [];
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition\Exception;
/**
* This exception is usually not encountered by the end-user, but only used
* internally to signal the parent scope to unset a key.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class UnsetKeyException extends Exception
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition\Exception;
/**
* This exception is thrown if an invalid type is encountered.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class InvalidTypeException extends InvalidConfigurationException
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition\Exception;
/**
* A very general exception which can be thrown whenever non of the more specific
* exceptions is suitable.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class InvalidConfigurationException extends Exception
{
private $path;
private $containsHints = false;
public function setPath($path)
{
$this->path = $path;
}
public function getPath()
{
return $this->path;
}
/**
* Adds extra information that is suffixed to the original exception message.
*/
public function addHint(string $hint)
{
if (!$this->containsHints) {
$this->message .= "\nHint: ".$hint;
$this->containsHints = true;
} else {
$this->message .= ', '.$hint;
}
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition\Exception;
/**
* Base exception for all configuration exceptions.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class Exception extends \RuntimeException
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition\Exception;
/**
* This exception is thrown when a configuration path is overwritten from a
* subsequent configuration file, but the entry node specifically forbids this.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class ForbiddenOverwriteException extends InvalidConfigurationException
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition\Exception;
/**
* Thrown when an error is detected in a node Definition.
*
* @author Victor Berchet <victor.berchet@suumit.com>
*/
class InvalidDefinitionException extends Exception
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition\Exception;
/**
* This exception is thrown whenever the key of an array is not unique. This can
* only be the case if the configuration is coming from an XML file.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class DuplicateKeyException extends InvalidConfigurationException
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition;
use Symfony\Component\Config\Definition\Exception\ForbiddenOverwriteException;
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
use Symfony\Component\Config\Definition\Exception\InvalidTypeException;
/**
* Common Interface among all nodes.
*
* In most cases, it is better to inherit from BaseNode instead of implementing
* this interface yourself.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
interface NodeInterface
{
/**
* Returns the name of the node.
*
* @return string The name of the node
*/
public function getName();
/**
* Returns the path of the node.
*
* @return string The node path
*/
public function getPath();
/**
* Returns true when the node is required.
*
* @return bool If the node is required
*/
public function isRequired();
/**
* Returns true when the node has a default value.
*
* @return bool If the node has a default value
*/
public function hasDefaultValue();
/**
* Returns the default value of the node.
*
* @return mixed The default value
*
* @throws \RuntimeException if the node has no default value
*/
public function getDefaultValue();
/**
* Normalizes a value.
*
* @param mixed $value The value to normalize
*
* @return mixed The normalized value
*
* @throws InvalidTypeException if the value type is invalid
*/
public function normalize($value);
/**
* Merges two values together.
*
* @param mixed $leftSide
* @param mixed $rightSide
*
* @return mixed The merged value
*
* @throws ForbiddenOverwriteException if the configuration path cannot be overwritten
* @throws InvalidTypeException if the value type is invalid
*/
public function merge($leftSide, $rightSide);
/**
* Finalizes a value.
*
* @param mixed $value The value to finalize
*
* @return mixed The finalized value
*
* @throws InvalidTypeException if the value type is invalid
* @throws InvalidConfigurationException if the value is invalid configuration
*/
public function finalize($value);
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition;
/**
* This interface must be implemented by nodes which can be used as prototypes.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
interface PrototypeNodeInterface extends NodeInterface
{
/**
* Sets the name of the node.
*/
public function setName(string $name);
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition;
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
use Symfony\Component\Config\Definition\Exception\InvalidTypeException;
use Symfony\Component\Config\Definition\Exception\UnsetKeyException;
/**
* Represents an Array node in the config tree.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class ArrayNode extends BaseNode implements PrototypeNodeInterface
{
protected $xmlRemappings = [];
protected $children = [];
protected $allowFalse = false;
protected $allowNewKeys = true;
protected $addIfNotSet = false;
protected $performDeepMerging = true;
protected $ignoreExtraKeys = false;
protected $removeExtraKeys = true;
protected $normalizeKeys = true;
public function setNormalizeKeys($normalizeKeys)
{
$this->normalizeKeys = (bool) $normalizeKeys;
}
/**
* {@inheritdoc}
*
* Namely, you mostly have foo_bar in YAML while you have foo-bar in XML.
* After running this method, all keys are normalized to foo_bar.
*
* If you have a mixed key like foo-bar_moo, it will not be altered.
* The key will also not be altered if the target key already exists.
*/
protected function preNormalize($value)
{
if (!$this->normalizeKeys || !\is_array($value)) {
return $value;
}
$normalized = [];
foreach ($value as $k => $v) {
if (false !== strpos($k, '-') && false === strpos($k, '_') && !\array_key_exists($normalizedKey = str_replace('-', '_', $k), $value)) {
$normalized[$normalizedKey] = $v;
} else {
$normalized[$k] = $v;
}
}
return $normalized;
}
/**
* Retrieves the children of this node.
*
* @return array The children
*/
public function getChildren()
{
return $this->children;
}
/**
* Sets the xml remappings that should be performed.
*
* @param array $remappings An array of the form [[string, string]]
*/
public function setXmlRemappings(array $remappings)
{
$this->xmlRemappings = $remappings;
}
/**
* Gets the xml remappings that should be performed.
*
* @return array an array of the form [[string, string]]
*/
public function getXmlRemappings()
{
return $this->xmlRemappings;
}
/**
* Sets whether to add default values for this array if it has not been
* defined in any of the configuration files.
*/
public function setAddIfNotSet(bool $boolean)
{
$this->addIfNotSet = $boolean;
}
/**
* Sets whether false is allowed as value indicating that the array should be unset.
*/
public function setAllowFalse(bool $allow)
{
$this->allowFalse = $allow;
}
/**
* Sets whether new keys can be defined in subsequent configurations.
*/
public function setAllowNewKeys(bool $allow)
{
$this->allowNewKeys = $allow;
}
/**
* Sets if deep merging should occur.
*/
public function setPerformDeepMerging(bool $boolean)
{
$this->performDeepMerging = $boolean;
}
/**
* Whether extra keys should just be ignored without an exception.
*
* @param bool $boolean To allow extra keys
* @param bool $remove To remove extra keys
*/
public function setIgnoreExtraKeys(bool $boolean, bool $remove = true)
{
$this->ignoreExtraKeys = $boolean;
$this->removeExtraKeys = $this->ignoreExtraKeys && $remove;
}
/**
* {@inheritdoc}
*/
public function setName(string $name)
{
$this->name = $name;
}
/**
* {@inheritdoc}
*/
public function hasDefaultValue()
{
return $this->addIfNotSet;
}
/**
* {@inheritdoc}
*/
public function getDefaultValue()
{
if (!$this->hasDefaultValue()) {
throw new \RuntimeException(sprintf('The node at path "%s" has no default value.', $this->getPath()));
}
$defaults = [];
foreach ($this->children as $name => $child) {
if ($child->hasDefaultValue()) {
$defaults[$name] = $child->getDefaultValue();
}
}
return $defaults;
}
/**
* Adds a child node.
*
* @throws \InvalidArgumentException when the child node has no name
* @throws \InvalidArgumentException when the child node's name is not unique
*/
public function addChild(NodeInterface $node)
{
$name = $node->getName();
if (!\strlen($name)) {
throw new \InvalidArgumentException('Child nodes must be named.');
}
if (isset($this->children[$name])) {
throw new \InvalidArgumentException(sprintf('A child node named "%s" already exists.', $name));
}
$this->children[$name] = $node;
}
/**
* Finalizes the value of this node.
*
* @param mixed $value
*
* @return mixed The finalised value
*
* @throws UnsetKeyException
* @throws InvalidConfigurationException if the node doesn't have enough children
*/
protected function finalizeValue($value)
{
if (false === $value) {
throw new UnsetKeyException(sprintf('Unsetting key for path "%s", value: %s.', $this->getPath(), json_encode($value)));
}
foreach ($this->children as $name => $child) {
if (!\array_key_exists($name, $value)) {
if ($child->isRequired()) {
$message = sprintf('The child config "%s" under "%s" must be configured', $name, $this->getPath());
if ($child->getInfo()) {
$message .= sprintf(": %s", $child->getInfo());
} else {
$message .= '.';
}
$ex = new InvalidConfigurationException($message);
$ex->setPath($this->getPath());
throw $ex;
}
if ($child->hasDefaultValue()) {
$value[$name] = $child->getDefaultValue();
}
continue;
}
if ($child->isDeprecated()) {
$deprecation = $child->getDeprecation($name, $this->getPath());
trigger_deprecation($deprecation['package'], $deprecation['version'], $deprecation['message']);
}
try {
$value[$name] = $child->finalize($value[$name]);
} catch (UnsetKeyException $e) {
unset($value[$name]);
}
}
return $value;
}
/**
* Validates the type of the value.
*
* @param mixed $value
*
* @throws InvalidTypeException
*/
protected function validateType($value)
{
if (!\is_array($value) && (!$this->allowFalse || false !== $value)) {
$ex = new InvalidTypeException(sprintf('Invalid type for path "%s". Expected "array", but got "%s"', $this->getPath(), get_debug_type($value)));
if ($hint = $this->getInfo()) {
$ex->addHint($hint);
}
$ex->setPath($this->getPath());
throw $ex;
}
}
/**
* Normalizes the value.
*
* @param mixed $value The value to normalize
*
* @return mixed The normalized value
*
* @throws InvalidConfigurationException
*/
protected function normalizeValue($value)
{
if (false === $value) {
return $value;
}
$value = $this->remapXml($value);
$normalized = [];
foreach ($value as $name => $val) {
if (isset($this->children[$name])) {
try {
$normalized[$name] = $this->children[$name]->normalize($val);
} catch (UnsetKeyException $e) {
}
unset($value[$name]);
} elseif (!$this->removeExtraKeys) {
$normalized[$name] = $val;
}
}
// if extra fields are present, throw exception
if (\count($value) && !$this->ignoreExtraKeys) {
$proposals = array_keys($this->children);
sort($proposals);
$guesses = [];
foreach (array_keys($value) as $subject) {
$minScore = \INF;
foreach ($proposals as $proposal) {
$distance = levenshtein($subject, $proposal);
if ($distance <= $minScore && $distance < 3) {
$guesses[$proposal] = $distance;
$minScore = $distance;
}
}
}
$msg = sprintf('Unrecognized option%s "%s" under "%s"', 1 === \count($value) ? '' : 's', implode(', ', array_keys($value)), $this->getPath());
if (\count($guesses)) {
asort($guesses);
$msg .= sprintf('. Did you mean "%s"?', implode('", "', array_keys($guesses)));
} else {
$msg .= sprintf('. Available option%s %s "%s".', 1 === \count($proposals) ? '' : 's', 1 === \count($proposals) ? 'is' : 'are', implode('", "', $proposals));
}
$ex = new InvalidConfigurationException($msg);
$ex->setPath($this->getPath());
throw $ex;
}
return $normalized;
}
/**
* Remaps multiple singular values to a single plural value.
*
* @return array The remapped values
*/
protected function remapXml(array $value)
{
foreach ($this->xmlRemappings as [$singular, $plural]) {
if (!isset($value[$singular])) {
continue;
}
$value[$plural] = Processor::normalizeConfig($value, $singular, $plural);
unset($value[$singular]);
}
return $value;
}
/**
* Merges values together.
*
* @param mixed $leftSide The left side to merge
* @param mixed $rightSide The right side to merge
*
* @return mixed The merged values
*
* @throws InvalidConfigurationException
* @throws \RuntimeException
*/
protected function mergeValues($leftSide, $rightSide)
{
if (false === $rightSide) {
// if this is still false after the last config has been merged the
// finalization pass will take care of removing this key entirely
return false;
}
if (false === $leftSide || !$this->performDeepMerging) {
return $rightSide;
}
foreach ($rightSide as $k => $v) {
// no conflict
if (!\array_key_exists($k, $leftSide)) {
if (!$this->allowNewKeys) {
$ex = new InvalidConfigurationException(sprintf('You are not allowed to define new elements for path "%s". Please define all elements for this path in one config file. If you are trying to overwrite an element, make sure you redefine it with the same name.', $this->getPath()));
$ex->setPath($this->getPath());
throw $ex;
}
$leftSide[$k] = $v;
continue;
}
if (!isset($this->children[$k])) {
if (!$this->ignoreExtraKeys || $this->removeExtraKeys) {
throw new \RuntimeException('merge() expects a normalized config array.');
}
$leftSide[$k] = $v;
continue;
}
$leftSide[$k] = $this->children[$k]->merge($leftSide[$k], $v);
}
return $leftSide;
}
/**
* {@inheritdoc}
*/
protected function allowPlaceholders(): bool
{
return false;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition;
use Symfony\Component\Config\Definition\Exception\InvalidTypeException;
/**
* This node represents a Boolean value in the config tree.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class BooleanNode extends ScalarNode
{
/**
* {@inheritdoc}
*/
protected function validateType($value)
{
if (!\is_bool($value)) {
$ex = new InvalidTypeException(sprintf('Invalid type for path "%s". Expected "bool", but got "%s".', $this->getPath(), get_debug_type($value)));
if ($hint = $this->getInfo()) {
$ex->addHint($hint);
}
$ex->setPath($this->getPath());
throw $ex;
}
}
/**
* {@inheritdoc}
*/
protected function isValueEmpty($value)
{
// a boolean value cannot be empty
return false;
}
/**
* {@inheritdoc}
*/
protected function getValidPlaceholderTypes(): array
{
return ['bool'];
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition;
use Symfony\Component\Config\Definition\Exception\InvalidTypeException;
/**
* This node represents a scalar value in the config tree.
*
* The following values are considered scalars:
* * booleans
* * strings
* * null
* * integers
* * floats
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class ScalarNode extends VariableNode
{
/**
* {@inheritdoc}
*/
protected function validateType($value)
{
if (!is_scalar($value) && null !== $value) {
$ex = new InvalidTypeException(sprintf('Invalid type for path "%s". Expected "scalar", but got "%s".', $this->getPath(), get_debug_type($value)));
if ($hint = $this->getInfo()) {
$ex->addHint($hint);
}
$ex->setPath($this->getPath());
throw $ex;
}
}
/**
* {@inheritdoc}
*/
protected function isValueEmpty($value)
{
// assume environment variables are never empty (which in practice is likely to be true during runtime)
// not doing so breaks many configs that are valid today
if ($this->isHandlingPlaceholder()) {
return false;
}
return null === $value || '' === $value;
}
/**
* {@inheritdoc}
*/
protected function getValidPlaceholderTypes(): array
{
return ['bool', 'int', 'float', 'string'];
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition;
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
/**
* This node represents a numeric value in the config tree.
*
* @author David Jeanmonod <david.jeanmonod@gmail.com>
*/
class NumericNode extends ScalarNode
{
protected $min;
protected $max;
public function __construct(?string $name, NodeInterface $parent = null, $min = null, $max = null, string $pathSeparator = BaseNode::DEFAULT_PATH_SEPARATOR)
{
parent::__construct($name, $parent, $pathSeparator);
$this->min = $min;
$this->max = $max;
}
/**
* {@inheritdoc}
*/
protected function finalizeValue($value)
{
$value = parent::finalizeValue($value);
$errorMsg = null;
if (isset($this->min) && $value < $this->min) {
$errorMsg = sprintf('The value %s is too small for path "%s". Should be greater than or equal to %s', $value, $this->getPath(), $this->min);
}
if (isset($this->max) && $value > $this->max) {
$errorMsg = sprintf('The value %s is too big for path "%s". Should be less than or equal to %s', $value, $this->getPath(), $this->max);
}
if (isset($errorMsg)) {
$ex = new InvalidConfigurationException($errorMsg);
$ex->setPath($this->getPath());
throw $ex;
}
return $value;
}
/**
* {@inheritdoc}
*/
protected function isValueEmpty($value)
{
// a numeric value cannot be empty
return false;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition;
use Symfony\Component\Config\Definition\Exception\InvalidTypeException;
/**
* This node represents an integer value in the config tree.
*
* @author Jeanmonod David <david.jeanmonod@gmail.com>
*/
class IntegerNode extends NumericNode
{
/**
* {@inheritdoc}
*/
protected function validateType($value)
{
if (!\is_int($value)) {
$ex = new InvalidTypeException(sprintf('Invalid type for path "%s". Expected "int", but got "%s".', $this->getPath(), get_debug_type($value)));
if ($hint = $this->getInfo()) {
$ex->addHint($hint);
}
$ex->setPath($this->getPath());
throw $ex;
}
}
/**
* {@inheritdoc}
*/
protected function getValidPlaceholderTypes(): array
{
return ['int'];
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition;
/**
* Configuration interface.
*
* @author Victor Berchet <victor@suumit.com>
*/
interface ConfigurationInterface
{
/**
* Generates the configuration tree builder.
*
* @return \Symfony\Component\Config\Definition\Builder\TreeBuilder The tree builder
*/
public function getConfigTreeBuilder();
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition;
use Symfony\Component\Config\Definition\Exception\Exception;
use Symfony\Component\Config\Definition\Exception\ForbiddenOverwriteException;
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
use Symfony\Component\Config\Definition\Exception\InvalidTypeException;
use Symfony\Component\Config\Definition\Exception\UnsetKeyException;
/**
* The base node class.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
abstract class BaseNode implements NodeInterface
{
const DEFAULT_PATH_SEPARATOR = '.';
private static $placeholderUniquePrefixes = [];
private static $placeholders = [];
protected $name;
protected $parent;
protected $normalizationClosures = [];
protected $finalValidationClosures = [];
protected $allowOverwrite = true;
protected $required = false;
protected $deprecation = [];
protected $equivalentValues = [];
protected $attributes = [];
protected $pathSeparator;
private $handlingPlaceholder;
/**
* @throws \InvalidArgumentException if the name contains a period
*/
public function __construct(?string $name, NodeInterface $parent = null, string $pathSeparator = self::DEFAULT_PATH_SEPARATOR)
{
if (false !== strpos($name = (string) $name, $pathSeparator)) {
throw new \InvalidArgumentException('The name must not contain ".'.$pathSeparator.'".');
}
$this->name = $name;
$this->parent = $parent;
$this->pathSeparator = $pathSeparator;
}
/**
* Register possible (dummy) values for a dynamic placeholder value.
*
* Matching configuration values will be processed with a provided value, one by one. After a provided value is
* successfully processed the configuration value is returned as is, thus preserving the placeholder.
*
* @internal
*/
public static function setPlaceholder(string $placeholder, array $values): void
{
if (!$values) {
throw new \InvalidArgumentException('At least one value must be provided.');
}
self::$placeholders[$placeholder] = $values;
}
/**
* Adds a common prefix for dynamic placeholder values.
*
* Matching configuration values will be skipped from being processed and are returned as is, thus preserving the
* placeholder. An exact match provided by {@see setPlaceholder()} might take precedence.
*
* @internal
*/
public static function setPlaceholderUniquePrefix(string $prefix): void
{
self::$placeholderUniquePrefixes[] = $prefix;
}
/**
* Resets all current placeholders available.
*
* @internal
*/
public static function resetPlaceholders(): void
{
self::$placeholderUniquePrefixes = [];
self::$placeholders = [];
}
public function setAttribute(string $key, $value)
{
$this->attributes[$key] = $value;
}
/**
* @return mixed
*/
public function getAttribute(string $key, $default = null)
{
return isset($this->attributes[$key]) ? $this->attributes[$key] : $default;
}
/**
* @return bool
*/
public function hasAttribute(string $key)
{
return isset($this->attributes[$key]);
}
/**
* @return array
*/
public function getAttributes()
{
return $this->attributes;
}
public function setAttributes(array $attributes)
{
$this->attributes = $attributes;
}
public function removeAttribute(string $key)
{
unset($this->attributes[$key]);
}
/**
* Sets an info message.
*/
public function setInfo(string $info)
{
$this->setAttribute('info', $info);
}
/**
* Returns info message.
*
* @return string|null The info text
*/
public function getInfo()
{
return $this->getAttribute('info');
}
/**
* Sets the example configuration for this node.
*
* @param string|array $example
*/
public function setExample($example)
{
$this->setAttribute('example', $example);
}
/**
* Retrieves the example configuration for this node.
*
* @return string|array|null The example
*/
public function getExample()
{
return $this->getAttribute('example');
}
/**
* Adds an equivalent value.
*
* @param mixed $originalValue
* @param mixed $equivalentValue
*/
public function addEquivalentValue($originalValue, $equivalentValue)
{
$this->equivalentValues[] = [$originalValue, $equivalentValue];
}
/**
* Set this node as required.
*
* @param bool $boolean Required node
*/
public function setRequired(bool $boolean)
{
$this->required = $boolean;
}
/**
* Sets this node as deprecated.
*
* @param string $package The name of the composer package that is triggering the deprecation
* @param string $version The version of the package that introduced the deprecation
* @param string $message the deprecation message to use
*
* You can use %node% and %path% placeholders in your message to display,
* respectively, the node name and its complete path
*/
public function setDeprecated(?string $package/*, string $version, string $message = 'The child node "%node%" at path "%path%" is deprecated.' */)
{
$args = \func_get_args();
if (\func_num_args() < 2) {
trigger_deprecation('symfony/config', '5.1', 'The signature of method "%s()" requires 3 arguments: "string $package, string $version, string $message", not defining them is deprecated.', __METHOD__);
if (!isset($args[0])) {
trigger_deprecation('symfony/config', '5.1', 'Passing a null message to un-deprecate a node is deprecated.');
$this->deprecation = [];
return;
}
$message = (string) $args[0];
$package = $version = '';
} else {
$package = (string) $args[0];
$version = (string) $args[1];
$message = (string) ($args[2] ?? 'The child node "%node%" at path "%path%" is deprecated.');
}
$this->deprecation = [
'package' => $package,
'version' => $version,
'message' => $message,
];
}
/**
* Sets if this node can be overridden.
*/
public function setAllowOverwrite(bool $allow)
{
$this->allowOverwrite = $allow;
}
/**
* Sets the closures used for normalization.
*
* @param \Closure[] $closures An array of Closures used for normalization
*/
public function setNormalizationClosures(array $closures)
{
$this->normalizationClosures = $closures;
}
/**
* Sets the closures used for final validation.
*
* @param \Closure[] $closures An array of Closures used for final validation
*/
public function setFinalValidationClosures(array $closures)
{
$this->finalValidationClosures = $closures;
}
/**
* {@inheritdoc}
*/
public function isRequired()
{
return $this->required;
}
/**
* Checks if this node is deprecated.
*
* @return bool
*/
public function isDeprecated()
{
return (bool) $this->deprecation;
}
/**
* Returns the deprecated message.
*
* @param string $node the configuration node name
* @param string $path the path of the node
*
* @return string
*
* @deprecated since Symfony 5.1, use "getDeprecation()" instead.
*/
public function getDeprecationMessage(string $node, string $path)
{
trigger_deprecation('symfony/config', '5.1', 'The "%s()" method is deprecated, use "getDeprecation()" instead.', __METHOD__);
return $this->getDeprecation($node, $path)['message'];
}
/**
* @param string $node The configuration node name
* @param string $path The path of the node
*/
public function getDeprecation(string $node, string $path): array
{
return [
'package' => $this->deprecation['package'] ?? '',
'version' => $this->deprecation['version'] ?? '',
'message' => strtr($this->deprecation['message'] ?? '', ['%node%' => $node, '%path%' => $path]),
];
}
/**
* {@inheritdoc}
*/
public function getName()
{
return $this->name;
}
/**
* {@inheritdoc}
*/
public function getPath()
{
if (null !== $this->parent) {
return $this->parent->getPath().$this->pathSeparator.$this->name;
}
return $this->name;
}
/**
* {@inheritdoc}
*/
final public function merge($leftSide, $rightSide)
{
if (!$this->allowOverwrite) {
throw new ForbiddenOverwriteException(sprintf('Configuration path "%s" cannot be overwritten. You have to define all options for this path, and any of its sub-paths in one configuration section.', $this->getPath()));
}
if ($leftSide !== $leftPlaceholders = self::resolvePlaceholderValue($leftSide)) {
foreach ($leftPlaceholders as $leftPlaceholder) {
$this->handlingPlaceholder = $leftSide;
try {
$this->merge($leftPlaceholder, $rightSide);
} finally {
$this->handlingPlaceholder = null;
}
}
return $rightSide;
}
if ($rightSide !== $rightPlaceholders = self::resolvePlaceholderValue($rightSide)) {
foreach ($rightPlaceholders as $rightPlaceholder) {
$this->handlingPlaceholder = $rightSide;
try {
$this->merge($leftSide, $rightPlaceholder);
} finally {
$this->handlingPlaceholder = null;
}
}
return $rightSide;
}
$this->doValidateType($leftSide);
$this->doValidateType($rightSide);
return $this->mergeValues($leftSide, $rightSide);
}
/**
* {@inheritdoc}
*/
final public function normalize($value)
{
$value = $this->preNormalize($value);
// run custom normalization closures
foreach ($this->normalizationClosures as $closure) {
$value = $closure($value);
}
// resolve placeholder value
if ($value !== $placeholders = self::resolvePlaceholderValue($value)) {
foreach ($placeholders as $placeholder) {
$this->handlingPlaceholder = $value;
try {
$this->normalize($placeholder);
} finally {
$this->handlingPlaceholder = null;
}
}
return $value;
}
// replace value with their equivalent
foreach ($this->equivalentValues as $data) {
if ($data[0] === $value) {
$value = $data[1];
}
}
// validate type
$this->doValidateType($value);
// normalize value
return $this->normalizeValue($value);
}
/**
* Normalizes the value before any other normalization is applied.
*
* @param mixed $value
*
* @return mixed The normalized array value
*/
protected function preNormalize($value)
{
return $value;
}
/**
* Returns parent node for this node.
*
* @return NodeInterface|null
*/
public function getParent()
{
return $this->parent;
}
/**
* {@inheritdoc}
*/
final public function finalize($value)
{
if ($value !== $placeholders = self::resolvePlaceholderValue($value)) {
foreach ($placeholders as $placeholder) {
$this->handlingPlaceholder = $value;
try {
$this->finalize($placeholder);
} finally {
$this->handlingPlaceholder = null;
}
}
return $value;
}
$this->doValidateType($value);
$value = $this->finalizeValue($value);
// Perform validation on the final value if a closure has been set.
// The closure is also allowed to return another value.
foreach ($this->finalValidationClosures as $closure) {
try {
$value = $closure($value);
} catch (Exception $e) {
if ($e instanceof UnsetKeyException && null !== $this->handlingPlaceholder) {
continue;
}
throw $e;
} catch (\Exception $e) {
throw new InvalidConfigurationException(sprintf('Invalid configuration for path "%s": ', $this->getPath()).$e->getMessage(), $e->getCode(), $e);
}
}
return $value;
}
/**
* Validates the type of a Node.
*
* @param mixed $value The value to validate
*
* @throws InvalidTypeException when the value is invalid
*/
abstract protected function validateType($value);
/**
* Normalizes the value.
*
* @param mixed $value The value to normalize
*
* @return mixed The normalized value
*/
abstract protected function normalizeValue($value);
/**
* Merges two values together.
*
* @param mixed $leftSide
* @param mixed $rightSide
*
* @return mixed The merged value
*/
abstract protected function mergeValues($leftSide, $rightSide);
/**
* Finalizes a value.
*
* @param mixed $value The value to finalize
*
* @return mixed The finalized value
*/
abstract protected function finalizeValue($value);
/**
* Tests if placeholder values are allowed for this node.
*/
protected function allowPlaceholders(): bool
{
return true;
}
/**
* Tests if a placeholder is being handled currently.
*/
protected function isHandlingPlaceholder(): bool
{
return null !== $this->handlingPlaceholder;
}
/**
* Gets allowed dynamic types for this node.
*/
protected function getValidPlaceholderTypes(): array
{
return [];
}
private static function resolvePlaceholderValue($value)
{
if (\is_string($value)) {
if (isset(self::$placeholders[$value])) {
return self::$placeholders[$value];
}
foreach (self::$placeholderUniquePrefixes as $placeholderUniquePrefix) {
if (0 === strpos($value, $placeholderUniquePrefix)) {
return [];
}
}
}
return $value;
}
private function doValidateType($value): void
{
if (null !== $this->handlingPlaceholder && !$this->allowPlaceholders()) {
$e = new InvalidTypeException(sprintf('A dynamic value is not compatible with a "%s" node type at path "%s".', static::class, $this->getPath()));
$e->setPath($this->getPath());
throw $e;
}
if (null === $this->handlingPlaceholder || null === $value) {
$this->validateType($value);
return;
}
$knownTypes = array_keys(self::$placeholders[$this->handlingPlaceholder]);
$validTypes = $this->getValidPlaceholderTypes();
if ($validTypes && array_diff($knownTypes, $validTypes)) {
$e = new InvalidTypeException(sprintf(
'Invalid type for path "%s". Expected %s, but got %s.',
$this->getPath(),
1 === \count($validTypes) ? '"'.reset($validTypes).'"' : 'one of "'.implode('", "', $validTypes).'"',
1 === \count($knownTypes) ? '"'.reset($knownTypes).'"' : 'one of "'.implode('", "', $knownTypes).'"'
));
if ($hint = $this->getInfo()) {
$e->addHint($hint);
}
$e->setPath($this->getPath());
throw $e;
}
$this->validateType($value);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition;
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
/**
* Node which only allows a finite set of values.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class EnumNode extends ScalarNode
{
private $values;
public function __construct(?string $name, NodeInterface $parent = null, array $values = [], string $pathSeparator = BaseNode::DEFAULT_PATH_SEPARATOR)
{
$values = array_unique($values);
if (empty($values)) {
throw new \InvalidArgumentException('$values must contain at least one element.');
}
parent::__construct($name, $parent, $pathSeparator);
$this->values = $values;
}
public function getValues()
{
return $this->values;
}
protected function finalizeValue($value)
{
$value = parent::finalizeValue($value);
if (!\in_array($value, $this->values, true)) {
$ex = new InvalidConfigurationException(sprintf('The value %s is not allowed for path "%s". Permissible values: %s', json_encode($value), $this->getPath(), implode(', ', array_map('json_encode', $this->values))));
$ex->setPath($this->getPath());
throw $ex;
}
return $value;
}
/**
* {@inheritdoc}
*/
protected function allowPlaceholders(): bool
{
return false;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition;
use Symfony\Component\Config\Definition\Exception\InvalidTypeException;
/**
* This node represents a float value in the config tree.
*
* @author Jeanmonod David <david.jeanmonod@gmail.com>
*/
class FloatNode extends NumericNode
{
/**
* {@inheritdoc}
*/
protected function validateType($value)
{
// Integers are also accepted, we just cast them
if (\is_int($value)) {
$value = (float) $value;
}
if (!\is_float($value)) {
$ex = new InvalidTypeException(sprintf('Invalid type for path "%s". Expected "float", but got "%s".', $this->getPath(), get_debug_type($value)));
if ($hint = $this->getInfo()) {
$ex->addHint($hint);
}
$ex->setPath($this->getPath());
throw $ex;
}
}
/**
* {@inheritdoc}
*/
protected function getValidPlaceholderTypes(): array
{
return ['float'];
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition;
use Symfony\Component\Config\Definition\Exception\DuplicateKeyException;
use Symfony\Component\Config\Definition\Exception\Exception;
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
use Symfony\Component\Config\Definition\Exception\UnsetKeyException;
/**
* Represents a prototyped Array node in the config tree.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class PrototypedArrayNode extends ArrayNode
{
protected $prototype;
protected $keyAttribute;
protected $removeKeyAttribute = false;
protected $minNumberOfElements = 0;
protected $defaultValue = [];
protected $defaultChildren;
/**
* @var NodeInterface[] An array of the prototypes of the simplified value children
*/
private $valuePrototypes = [];
/**
* Sets the minimum number of elements that a prototype based node must
* contain. By default this is zero, meaning no elements.
*/
public function setMinNumberOfElements(int $number)
{
$this->minNumberOfElements = $number;
}
/**
* Sets the attribute which value is to be used as key.
*
* This is useful when you have an indexed array that should be an
* associative array. You can select an item from within the array
* to be the key of the particular item. For example, if "id" is the
* "key", then:
*
* [
* ['id' => 'my_name', 'foo' => 'bar'],
* ];
*
* becomes
*
* [
* 'my_name' => ['foo' => 'bar'],
* ];
*
* If you'd like "'id' => 'my_name'" to still be present in the resulting
* array, then you can set the second argument of this method to false.
*
* @param string $attribute The name of the attribute which value is to be used as a key
* @param bool $remove Whether or not to remove the key
*/
public function setKeyAttribute(string $attribute, bool $remove = true)
{
$this->keyAttribute = $attribute;
$this->removeKeyAttribute = $remove;
}
/**
* Retrieves the name of the attribute which value should be used as key.
*
* @return string|null The name of the attribute
*/
public function getKeyAttribute()
{
return $this->keyAttribute;
}
/**
* Sets the default value of this node.
*/
public function setDefaultValue(array $value)
{
$this->defaultValue = $value;
}
/**
* {@inheritdoc}
*/
public function hasDefaultValue()
{
return true;
}
/**
* Adds default children when none are set.
*
* @param int|string|array|null $children The number of children|The child name|The children names to be added
*/
public function setAddChildrenIfNoneSet($children = ['defaults'])
{
if (null === $children) {
$this->defaultChildren = ['defaults'];
} else {
$this->defaultChildren = \is_int($children) && $children > 0 ? range(1, $children) : (array) $children;
}
}
/**
* {@inheritdoc}
*
* The default value could be either explicited or derived from the prototype
* default value.
*/
public function getDefaultValue()
{
if (null !== $this->defaultChildren) {
$default = $this->prototype->hasDefaultValue() ? $this->prototype->getDefaultValue() : [];
$defaults = [];
foreach (array_values($this->defaultChildren) as $i => $name) {
$defaults[null === $this->keyAttribute ? $i : $name] = $default;
}
return $defaults;
}
return $this->defaultValue;
}
/**
* Sets the node prototype.
*/
public function setPrototype(PrototypeNodeInterface $node)
{
$this->prototype = $node;
}
/**
* Retrieves the prototype.
*
* @return PrototypeNodeInterface The prototype
*/
public function getPrototype()
{
return $this->prototype;
}
/**
* Disable adding concrete children for prototyped nodes.
*
* @throws Exception
*/
public function addChild(NodeInterface $node)
{
throw new Exception('A prototyped array node can not have concrete children.');
}
/**
* Finalizes the value of this node.
*
* @param mixed $value
*
* @return mixed The finalized value
*
* @throws UnsetKeyException
* @throws InvalidConfigurationException if the node doesn't have enough children
*/
protected function finalizeValue($value)
{
if (false === $value) {
throw new UnsetKeyException(sprintf('Unsetting key for path "%s", value: %s.', $this->getPath(), json_encode($value)));
}
foreach ($value as $k => $v) {
$prototype = $this->getPrototypeForChild($k);
try {
$value[$k] = $prototype->finalize($v);
} catch (UnsetKeyException $e) {
unset($value[$k]);
}
}
if (\count($value) < $this->minNumberOfElements) {
$ex = new InvalidConfigurationException(sprintf('The path "%s" should have at least %d element(s) defined.', $this->getPath(), $this->minNumberOfElements));
$ex->setPath($this->getPath());
throw $ex;
}
return $value;
}
/**
* Normalizes the value.
*
* @param mixed $value The value to normalize
*
* @return mixed The normalized value
*
* @throws InvalidConfigurationException
* @throws DuplicateKeyException
*/
protected function normalizeValue($value)
{
if (false === $value) {
return $value;
}
$value = $this->remapXml($value);
$isAssoc = array_keys($value) !== range(0, \count($value) - 1);
$normalized = [];
foreach ($value as $k => $v) {
if (null !== $this->keyAttribute && \is_array($v)) {
if (!isset($v[$this->keyAttribute]) && \is_int($k) && !$isAssoc) {
$ex = new InvalidConfigurationException(sprintf('The attribute "%s" must be set for path "%s".', $this->keyAttribute, $this->getPath()));
$ex->setPath($this->getPath());
throw $ex;
} elseif (isset($v[$this->keyAttribute])) {
$k = $v[$this->keyAttribute];
// remove the key attribute when required
if ($this->removeKeyAttribute) {
unset($v[$this->keyAttribute]);
}
// if only "value" is left
if (array_keys($v) === ['value']) {
$v = $v['value'];
if ($this->prototype instanceof ArrayNode && ($children = $this->prototype->getChildren()) && \array_key_exists('value', $children)) {
$valuePrototype = current($this->valuePrototypes) ?: clone $children['value'];
$valuePrototype->parent = $this;
$originalClosures = $this->prototype->normalizationClosures;
if (\is_array($originalClosures)) {
$valuePrototypeClosures = $valuePrototype->normalizationClosures;
$valuePrototype->normalizationClosures = \is_array($valuePrototypeClosures) ? array_merge($originalClosures, $valuePrototypeClosures) : $originalClosures;
}
$this->valuePrototypes[$k] = $valuePrototype;
}
}
}
if (\array_key_exists($k, $normalized)) {
$ex = new DuplicateKeyException(sprintf('Duplicate key "%s" for path "%s".', $k, $this->getPath()));
$ex->setPath($this->getPath());
throw $ex;
}
}
$prototype = $this->getPrototypeForChild($k);
if (null !== $this->keyAttribute || $isAssoc) {
$normalized[$k] = $prototype->normalize($v);
} else {
$normalized[] = $prototype->normalize($v);
}
}
return $normalized;
}
/**
* Merges values together.
*
* @param mixed $leftSide The left side to merge
* @param mixed $rightSide The right side to merge
*
* @return mixed The merged values
*
* @throws InvalidConfigurationException
* @throws \RuntimeException
*/
protected function mergeValues($leftSide, $rightSide)
{
if (false === $rightSide) {
// if this is still false after the last config has been merged the
// finalization pass will take care of removing this key entirely
return false;
}
if (false === $leftSide || !$this->performDeepMerging) {
return $rightSide;
}
foreach ($rightSide as $k => $v) {
// prototype, and key is irrelevant, append the element
if (null === $this->keyAttribute) {
$leftSide[] = $v;
continue;
}
// no conflict
if (!\array_key_exists($k, $leftSide)) {
if (!$this->allowNewKeys) {
$ex = new InvalidConfigurationException(sprintf('You are not allowed to define new elements for path "%s". Please define all elements for this path in one config file.', $this->getPath()));
$ex->setPath($this->getPath());
throw $ex;
}
$leftSide[$k] = $v;
continue;
}
$prototype = $this->getPrototypeForChild($k);
$leftSide[$k] = $prototype->merge($leftSide[$k], $v);
}
return $leftSide;
}
/**
* Returns a prototype for the child node that is associated to $key in the value array.
* For general child nodes, this will be $this->prototype.
* But if $this->removeKeyAttribute is true and there are only two keys in the child node:
* one is same as this->keyAttribute and the other is 'value', then the prototype will be different.
*
* For example, assume $this->keyAttribute is 'name' and the value array is as follows:
*
* [
* [
* 'name' => 'name001',
* 'value' => 'value001'
* ]
* ]
*
* Now, the key is 0 and the child node is:
*
* [
* 'name' => 'name001',
* 'value' => 'value001'
* ]
*
* When normalizing the value array, the 'name' element will removed from the child node
* and its value becomes the new key of the child node:
*
* [
* 'name001' => ['value' => 'value001']
* ]
*
* Now only 'value' element is left in the child node which can be further simplified into a string:
*
* ['name001' => 'value001']
*
* Now, the key becomes 'name001' and the child node becomes 'value001' and
* the prototype of child node 'name001' should be a ScalarNode instead of an ArrayNode instance.
*
* @return mixed The prototype instance
*/
private function getPrototypeForChild(string $key)
{
$prototype = isset($this->valuePrototypes[$key]) ? $this->valuePrototypes[$key] : $this->prototype;
$prototype->setName($key);
return $prototype;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition\Dumper;
use Symfony\Component\Config\Definition\ArrayNode;
use Symfony\Component\Config\Definition\ConfigurationInterface;
use Symfony\Component\Config\Definition\EnumNode;
use Symfony\Component\Config\Definition\NodeInterface;
use Symfony\Component\Config\Definition\PrototypedArrayNode;
/**
* Dumps a XML reference configuration for the given configuration/node instance.
*
* @author Wouter J <waldio.webdesign@gmail.com>
*/
class XmlReferenceDumper
{
private $reference;
public function dump(ConfigurationInterface $configuration, string $namespace = null)
{
return $this->dumpNode($configuration->getConfigTreeBuilder()->buildTree(), $namespace);
}
public function dumpNode(NodeInterface $node, string $namespace = null)
{
$this->reference = '';
$this->writeNode($node, 0, true, $namespace);
$ref = $this->reference;
$this->reference = null;
return $ref;
}
private function writeNode(NodeInterface $node, int $depth = 0, bool $root = false, string $namespace = null)
{
$rootName = ($root ? 'config' : $node->getName());
$rootNamespace = ($namespace ?: ($root ? 'http://example.org/schema/dic/'.$node->getName() : null));
// xml remapping
if ($node->getParent()) {
$remapping = array_filter($node->getParent()->getXmlRemappings(), function ($mapping) use ($rootName) {
return $rootName === $mapping[1];
});
if (\count($remapping)) {
[$singular] = current($remapping);
$rootName = $singular;
}
}
$rootName = str_replace('_', '-', $rootName);
$rootAttributes = [];
$rootAttributeComments = [];
$rootChildren = [];
$rootComments = [];
if ($node instanceof ArrayNode) {
$children = $node->getChildren();
// comments about the root node
if ($rootInfo = $node->getInfo()) {
$rootComments[] = $rootInfo;
}
if ($rootNamespace) {
$rootComments[] = 'Namespace: '.$rootNamespace;
}
// render prototyped nodes
if ($node instanceof PrototypedArrayNode) {
$prototype = $node->getPrototype();
$info = 'prototype';
if (null !== $prototype->getInfo()) {
$info .= ': '.$prototype->getInfo();
}
array_unshift($rootComments, $info);
if ($key = $node->getKeyAttribute()) {
$rootAttributes[$key] = str_replace('-', ' ', $rootName).' '.$key;
}
if ($prototype instanceof PrototypedArrayNode) {
$prototype->setName($key ?? '');
$children = [$key => $prototype];
} elseif ($prototype instanceof ArrayNode) {
$children = $prototype->getChildren();
} else {
if ($prototype->hasDefaultValue()) {
$prototypeValue = $prototype->getDefaultValue();
} else {
switch (\get_class($prototype)) {
case 'Symfony\Component\Config\Definition\ScalarNode':
$prototypeValue = 'scalar value';
break;
case 'Symfony\Component\Config\Definition\FloatNode':
case 'Symfony\Component\Config\Definition\IntegerNode':
$prototypeValue = 'numeric value';
break;
case 'Symfony\Component\Config\Definition\BooleanNode':
$prototypeValue = 'true|false';
break;
case 'Symfony\Component\Config\Definition\EnumNode':
$prototypeValue = implode('|', array_map('json_encode', $prototype->getValues()));
break;
default:
$prototypeValue = 'value';
}
}
}
}
// get attributes and elements
foreach ($children as $child) {
if (!$child instanceof ArrayNode) {
// get attributes
// metadata
$name = str_replace('_', '-', $child->getName());
$value = '%%%%not_defined%%%%'; // use a string which isn't used in the normal world
// comments
$comments = [];
if ($info = $child->getInfo()) {
$comments[] = $info;
}
if ($example = $child->getExample()) {
$comments[] = 'Example: '.$example;
}
if ($child->isRequired()) {
$comments[] = 'Required';
}
if ($child->isDeprecated()) {
$deprecation = $child->getDeprecation($child->getName(), $node->getPath());
$comments[] = sprintf('Deprecated (%s)', ($deprecation['package'] || $deprecation['version'] ? "Since {$deprecation['package']} {$deprecation['version']}: " : '').$deprecation['message']);
}
if ($child instanceof EnumNode) {
$comments[] = 'One of '.implode('; ', array_map('json_encode', $child->getValues()));
}
if (\count($comments)) {
$rootAttributeComments[$name] = implode(";\n", $comments);
}
// default values
if ($child->hasDefaultValue()) {
$value = $child->getDefaultValue();
}
// append attribute
$rootAttributes[$name] = $value;
} else {
// get elements
$rootChildren[] = $child;
}
}
}
// render comments
// root node comment
if (\count($rootComments)) {
foreach ($rootComments as $comment) {
$this->writeLine('<!-- '.$comment.' -->', $depth);
}
}
// attribute comments
if (\count($rootAttributeComments)) {
foreach ($rootAttributeComments as $attrName => $comment) {
$commentDepth = $depth + 4 + \strlen($attrName) + 2;
$commentLines = explode("\n", $comment);
$multiline = (\count($commentLines) > 1);
$comment = implode(\PHP_EOL.str_repeat(' ', $commentDepth), $commentLines);
if ($multiline) {
$this->writeLine('<!--', $depth);
$this->writeLine($attrName.': '.$comment, $depth + 4);
$this->writeLine('-->', $depth);
} else {
$this->writeLine('<!-- '.$attrName.': '.$comment.' -->', $depth);
}
}
}
// render start tag + attributes
$rootIsVariablePrototype = isset($prototypeValue);
$rootIsEmptyTag = (0 === \count($rootChildren) && !$rootIsVariablePrototype);
$rootOpenTag = '<'.$rootName;
if (1 >= ($attributesCount = \count($rootAttributes))) {
if (1 === $attributesCount) {
$rootOpenTag .= sprintf(' %s="%s"', current(array_keys($rootAttributes)), $this->writeValue(current($rootAttributes)));
}
$rootOpenTag .= $rootIsEmptyTag ? ' />' : '>';
if ($rootIsVariablePrototype) {
$rootOpenTag .= $prototypeValue.'</'.$rootName.'>';
}
$this->writeLine($rootOpenTag, $depth);
} else {
$this->writeLine($rootOpenTag, $depth);
$i = 1;
foreach ($rootAttributes as $attrName => $attrValue) {
$attr = sprintf('%s="%s"', $attrName, $this->writeValue($attrValue));
$this->writeLine($attr, $depth + 4);
if ($attributesCount === $i++) {
$this->writeLine($rootIsEmptyTag ? '/>' : '>', $depth);
if ($rootIsVariablePrototype) {
$rootOpenTag .= $prototypeValue.'</'.$rootName.'>';
}
}
}
}
// render children tags
foreach ($rootChildren as $child) {
$this->writeLine('');
$this->writeNode($child, $depth + 4);
}
// render end tag
if (!$rootIsEmptyTag && !$rootIsVariablePrototype) {
$this->writeLine('');
$rootEndTag = '</'.$rootName.'>';
$this->writeLine($rootEndTag, $depth);
}
}
/**
* Outputs a single config reference line.
*/
private function writeLine(string $text, int $indent = 0)
{
$indent = \strlen($text) + $indent;
$format = '%'.$indent.'s';
$this->reference .= sprintf($format, $text).\PHP_EOL;
}
/**
* Renders the string conversion of the value.
*
* @param mixed $value
*/
private function writeValue($value): string
{
if ('%%%%not_defined%%%%' === $value) {
return '';
}
if (\is_string($value) || is_numeric($value)) {
return $value;
}
if (false === $value) {
return 'false';
}
if (true === $value) {
return 'true';
}
if (null === $value) {
return 'null';
}
if (empty($value)) {
return '';
}
if (\is_array($value)) {
return implode(',', $value);
}
return '';
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition\Dumper;
use Symfony\Component\Config\Definition\ArrayNode;
use Symfony\Component\Config\Definition\ConfigurationInterface;
use Symfony\Component\Config\Definition\EnumNode;
use Symfony\Component\Config\Definition\NodeInterface;
use Symfony\Component\Config\Definition\PrototypedArrayNode;
use Symfony\Component\Config\Definition\ScalarNode;
use Symfony\Component\Yaml\Inline;
/**
* Dumps a Yaml reference configuration for the given configuration/node instance.
*
* @author Kevin Bond <kevinbond@gmail.com>
*/
class YamlReferenceDumper
{
private $reference;
public function dump(ConfigurationInterface $configuration)
{
return $this->dumpNode($configuration->getConfigTreeBuilder()->buildTree());
}
public function dumpAtPath(ConfigurationInterface $configuration, string $path)
{
$rootNode = $node = $configuration->getConfigTreeBuilder()->buildTree();
foreach (explode('.', $path) as $step) {
if (!$node instanceof ArrayNode) {
throw new \UnexpectedValueException(sprintf('Unable to find node at path "%s.%s".', $rootNode->getName(), $path));
}
/** @var NodeInterface[] $children */
$children = $node instanceof PrototypedArrayNode ? $this->getPrototypeChildren($node) : $node->getChildren();
foreach ($children as $child) {
if ($child->getName() === $step) {
$node = $child;
continue 2;
}
}
throw new \UnexpectedValueException(sprintf('Unable to find node at path "%s.%s".', $rootNode->getName(), $path));
}
return $this->dumpNode($node);
}
public function dumpNode(NodeInterface $node)
{
$this->reference = '';
$this->writeNode($node);
$ref = $this->reference;
$this->reference = null;
return $ref;
}
private function writeNode(NodeInterface $node, NodeInterface $parentNode = null, int $depth = 0, bool $prototypedArray = false)
{
$comments = [];
$default = '';
$defaultArray = null;
$children = null;
$example = $node->getExample();
// defaults
if ($node instanceof ArrayNode) {
$children = $node->getChildren();
if ($node instanceof PrototypedArrayNode) {
$children = $this->getPrototypeChildren($node);
}
if (!$children) {
if ($node->hasDefaultValue() && \count($defaultArray = $node->getDefaultValue())) {
$default = '';
} elseif (!\is_array($example)) {
$default = '[]';
}
}
} elseif ($node instanceof EnumNode) {
$comments[] = 'One of '.implode('; ', array_map('json_encode', $node->getValues()));
$default = $node->hasDefaultValue() ? Inline::dump($node->getDefaultValue()) : '~';
} else {
$default = '~';
if ($node->hasDefaultValue()) {
$default = $node->getDefaultValue();
if (\is_array($default)) {
if (\count($defaultArray = $node->getDefaultValue())) {
$default = '';
} elseif (!\is_array($example)) {
$default = '[]';
}
} else {
$default = Inline::dump($default);
}
}
}
// required?
if ($node->isRequired()) {
$comments[] = 'Required';
}
// deprecated?
if ($node->isDeprecated()) {
$deprecation = $node->getDeprecation($node->getName(), $parentNode ? $parentNode->getPath() : $node->getPath());
$comments[] = sprintf('Deprecated (%s)', ($deprecation['package'] || $deprecation['version'] ? "Since {$deprecation['package']} {$deprecation['version']}: " : '').$deprecation['message']);
}
// example
if ($example && !\is_array($example)) {
$comments[] = 'Example: '.$example;
}
$default = '' != (string) $default ? ' '.$default : '';
$comments = \count($comments) ? '# '.implode(', ', $comments) : '';
$key = $prototypedArray ? '-' : $node->getName().':';
$text = rtrim(sprintf('%-21s%s %s', $key, $default, $comments), ' ');
if ($info = $node->getInfo()) {
$this->writeLine('');
// indenting multi-line info
$info = str_replace("\n", sprintf("\n%".($depth * 4).'s# ', ' '), $info);
$this->writeLine('# '.$info, $depth * 4);
}
$this->writeLine($text, $depth * 4);
// output defaults
if ($defaultArray) {
$this->writeLine('');
$message = \count($defaultArray) > 1 ? 'Defaults' : 'Default';
$this->writeLine('# '.$message.':', $depth * 4 + 4);
$this->writeArray($defaultArray, $depth + 1);
}
if (\is_array($example)) {
$this->writeLine('');
$message = \count($example) > 1 ? 'Examples' : 'Example';
$this->writeLine('# '.$message.':', $depth * 4 + 4);
$this->writeArray($example, $depth + 1);
}
if ($children) {
foreach ($children as $childNode) {
$this->writeNode($childNode, $node, $depth + 1, $node instanceof PrototypedArrayNode && !$node->getKeyAttribute());
}
}
}
/**
* Outputs a single config reference line.
*/
private function writeLine(string $text, int $indent = 0)
{
$indent = \strlen($text) + $indent;
$format = '%'.$indent.'s';
$this->reference .= sprintf($format, $text)."\n";
}
private function writeArray(array $array, int $depth)
{
$isIndexed = array_values($array) === $array;
foreach ($array as $key => $value) {
if (\is_array($value)) {
$val = '';
} else {
$val = $value;
}
if ($isIndexed) {
$this->writeLine('- '.$val, $depth * 4);
} else {
$this->writeLine(sprintf('%-20s %s', $key.':', $val), $depth * 4);
}
if (\is_array($value)) {
$this->writeArray($value, $depth + 1);
}
}
}
private function getPrototypeChildren(PrototypedArrayNode $node): array
{
$prototype = $node->getPrototype();
$key = $node->getKeyAttribute();
// Do not expand prototype if it isn't an array node nor uses attribute as key
if (!$key && !$prototype instanceof ArrayNode) {
return $node->getChildren();
}
if ($prototype instanceof ArrayNode) {
$keyNode = new ArrayNode($key, $node);
$children = $prototype->getChildren();
if ($prototype instanceof PrototypedArrayNode && $prototype->getKeyAttribute()) {
$children = $this->getPrototypeChildren($prototype);
}
// add children
foreach ($children as $childNode) {
$keyNode->addChild($childNode);
}
} else {
$keyNode = new ScalarNode($key, $node);
}
$info = 'Prototype';
if (null !== $prototype->getInfo()) {
$info .= ': '.$prototype->getInfo();
}
$keyNode->setInfo($info);
return [$key => $keyNode];
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config;
use Symfony\Component\Config\Resource\ResourceInterface;
use Symfony\Component\Filesystem\Exception\IOException;
use Symfony\Component\Filesystem\Filesystem;
/**
* ResourceCheckerConfigCache uses instances of ResourceCheckerInterface
* to check whether cached data is still fresh.
*
* @author Matthias Pigulla <mp@webfactory.de>
*/
class ResourceCheckerConfigCache implements ConfigCacheInterface
{
/**
* @var string
*/
private $file;
/**
* @var iterable|ResourceCheckerInterface[]
*/
private $resourceCheckers;
/**
* @param string $file The absolute cache path
* @param iterable|ResourceCheckerInterface[] $resourceCheckers The ResourceCheckers to use for the freshness check
*/
public function __construct(string $file, iterable $resourceCheckers = [])
{
$this->file = $file;
$this->resourceCheckers = $resourceCheckers;
}
/**
* {@inheritdoc}
*/
public function getPath()
{
return $this->file;
}
/**
* Checks if the cache is still fresh.
*
* This implementation will make a decision solely based on the ResourceCheckers
* passed in the constructor.
*
* The first ResourceChecker that supports a given resource is considered authoritative.
* Resources with no matching ResourceChecker will silently be ignored and considered fresh.
*
* @return bool true if the cache is fresh, false otherwise
*/
public function isFresh()
{
if (!is_file($this->file)) {
return false;
}
if ($this->resourceCheckers instanceof \Traversable && !$this->resourceCheckers instanceof \Countable) {
$this->resourceCheckers = iterator_to_array($this->resourceCheckers);
}
if (!\count($this->resourceCheckers)) {
return true; // shortcut - if we don't have any checkers we don't need to bother with the meta file at all
}
$metadata = $this->getMetaFile();
if (!is_file($metadata)) {
return false;
}
$meta = $this->safelyUnserialize($metadata);
if (false === $meta) {
return false;
}
$time = filemtime($this->file);
foreach ($meta as $resource) {
/* @var ResourceInterface $resource */
foreach ($this->resourceCheckers as $checker) {
if (!$checker->supports($resource)) {
continue; // next checker
}
if ($checker->isFresh($resource, $time)) {
break; // no need to further check this resource
}
return false; // cache is stale
}
// no suitable checker found, ignore this resource
}
return true;
}
/**
* Writes cache.
*
* @param string $content The content to write in the cache
* @param ResourceInterface[] $metadata An array of metadata
*
* @throws \RuntimeException When cache file can't be written
*/
public function write(string $content, array $metadata = null)
{
$mode = 0666;
$umask = umask();
$filesystem = new Filesystem();
$filesystem->dumpFile($this->file, $content);
try {
$filesystem->chmod($this->file, $mode, $umask);
} catch (IOException $e) {
// discard chmod failure (some filesystem may not support it)
}
if (null !== $metadata) {
$filesystem->dumpFile($this->getMetaFile(), serialize($metadata));
try {
$filesystem->chmod($this->getMetaFile(), $mode, $umask);
} catch (IOException $e) {
// discard chmod failure (some filesystem may not support it)
}
}
if (\function_exists('opcache_invalidate') && filter_var(ini_get('opcache.enable'), \FILTER_VALIDATE_BOOLEAN)) {
@opcache_invalidate($this->file, true);
}
}
/**
* Gets the meta file path.
*/
private function getMetaFile(): string
{
return $this->file.'.meta';
}
private function safelyUnserialize(string $file)
{
$meta = false;
$content = file_get_contents($file);
$signalingException = new \UnexpectedValueException();
$prevUnserializeHandler = ini_set('unserialize_callback_func', self::class.'::handleUnserializeCallback');
$prevErrorHandler = set_error_handler(function ($type, $msg, $file, $line, $context = []) use (&$prevErrorHandler, $signalingException) {
if (__FILE__ === $file) {
throw $signalingException;
}
return $prevErrorHandler ? $prevErrorHandler($type, $msg, $file, $line, $context) : false;
});
try {
$meta = unserialize($content);
} catch (\Throwable $e) {
if ($e !== $signalingException) {
throw $e;
}
} finally {
restore_error_handler();
ini_set('unserialize_callback_func', $prevUnserializeHandler);
}
return $meta;
}
/**
* @internal
*/
public static function handleUnserializeCallback($class)
{
trigger_error('Class not found: '.$class);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Loader;
/**
* LoaderInterface is the interface implemented by all loader classes.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
interface LoaderInterface
{
/**
* Loads a resource.
*
* @param mixed $resource The resource
*
* @throws \Exception If something went wrong
*/
public function load($resource, string $type = null);
/**
* Returns whether this class supports the given resource.
*
* @param mixed $resource A resource
*
* @return bool True if this class supports the given resource, false otherwise
*/
public function supports($resource, string $type = null);
/**
* Gets the loader resolver.
*
* @return LoaderResolverInterface A LoaderResolverInterface instance
*/
public function getResolver();
/**
* Sets the loader resolver.
*/
public function setResolver(LoaderResolverInterface $resolver);
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Loader;
use Symfony\Component\Config\Exception\LoaderLoadException;
/**
* Loader is the abstract class used by all built-in loaders.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
abstract class Loader implements LoaderInterface
{
protected $resolver;
/**
* {@inheritdoc}
*/
public function getResolver()
{
return $this->resolver;
}
/**
* {@inheritdoc}
*/
public function setResolver(LoaderResolverInterface $resolver)
{
$this->resolver = $resolver;
}
/**
* Imports a resource.
*
* @param mixed $resource A resource
* @param string|null $type The resource type or null if unknown
*
* @return mixed
*/
public function import($resource, string $type = null)
{
return $this->resolve($resource, $type)->load($resource, $type);
}
/**
* Finds a loader able to load an imported resource.
*
* @param mixed $resource A resource
* @param string|null $type The resource type or null if unknown
*
* @return $this|LoaderInterface
*
* @throws LoaderLoadException If no loader is found
*/
public function resolve($resource, string $type = null)
{
if ($this->supports($resource, $type)) {
return $this;
}
$loader = null === $this->resolver ? false : $this->resolver->resolve($resource, $type);
if (false === $loader) {
throw new LoaderLoadException($resource, null, null, null, $type);
}
return $loader;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Loader;
use Symfony\Component\Config\Exception\FileLoaderImportCircularReferenceException;
use Symfony\Component\Config\Exception\FileLocatorFileNotFoundException;
use Symfony\Component\Config\Exception\LoaderLoadException;
use Symfony\Component\Config\FileLocatorInterface;
use Symfony\Component\Config\Resource\FileExistenceResource;
use Symfony\Component\Config\Resource\GlobResource;
/**
* FileLoader is the abstract class used by all built-in loaders that are file based.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
abstract class FileLoader extends Loader
{
protected static $loading = [];
protected $locator;
private $currentDir;
public function __construct(FileLocatorInterface $locator)
{
$this->locator = $locator;
}
/**
* Sets the current directory.
*/
public function setCurrentDir(string $dir)
{
$this->currentDir = $dir;
}
/**
* Returns the file locator used by this loader.
*
* @return FileLocatorInterface
*/
public function getLocator()
{
return $this->locator;
}
/**
* Imports a resource.
*
* @param mixed $resource A Resource
* @param string|null $type The resource type or null if unknown
* @param bool $ignoreErrors Whether to ignore import errors or not
* @param string|null $sourceResource The original resource importing the new resource
* @param string|string[]|null $exclude Glob patterns to exclude from the import
*
* @return mixed
*
* @throws LoaderLoadException
* @throws FileLoaderImportCircularReferenceException
* @throws FileLocatorFileNotFoundException
*/
public function import($resource, string $type = null, bool $ignoreErrors = false, string $sourceResource = null, $exclude = null)
{
if (\is_string($resource) && \strlen($resource) !== $i = strcspn($resource, '*?{[')) {
$excluded = [];
foreach ((array) $exclude as $pattern) {
foreach ($this->glob($pattern, true, $_, false, true) as $path => $info) {
// normalize Windows slashes
$excluded[str_replace('\\', '/', $path)] = true;
}
}
$ret = [];
$isSubpath = 0 !== $i && false !== strpos(substr($resource, 0, $i), '/');
foreach ($this->glob($resource, false, $_, $ignoreErrors || !$isSubpath, false, $excluded) as $path => $info) {
if (null !== $res = $this->doImport($path, 'glob' === $type ? null : $type, $ignoreErrors, $sourceResource)) {
$ret[] = $res;
}
$isSubpath = true;
}
if ($isSubpath) {
return isset($ret[1]) ? $ret : (isset($ret[0]) ? $ret[0] : null);
}
}
return $this->doImport($resource, $type, $ignoreErrors, $sourceResource);
}
/**
* @internal
*/
protected function glob(string $pattern, bool $recursive, &$resource = null, bool $ignoreErrors = false, bool $forExclusion = false, array $excluded = [])
{
if (\strlen($pattern) === $i = strcspn($pattern, '*?{[')) {
$prefix = $pattern;
$pattern = '';
} elseif (0 === $i || false === strpos(substr($pattern, 0, $i), '/')) {
$prefix = '.';
$pattern = '/'.$pattern;
} else {
$prefix = \dirname(substr($pattern, 0, 1 + $i));
$pattern = substr($pattern, \strlen($prefix));
}
try {
$prefix = $this->locator->locate($prefix, $this->currentDir, true);
} catch (FileLocatorFileNotFoundException $e) {
if (!$ignoreErrors) {
throw $e;
}
$resource = [];
foreach ($e->getPaths() as $path) {
$resource[] = new FileExistenceResource($path);
}
return;
}
$resource = new GlobResource($prefix, $pattern, $recursive, $forExclusion, $excluded);
yield from $resource;
}
private function doImport($resource, string $type = null, bool $ignoreErrors = false, string $sourceResource = null)
{
try {
$loader = $this->resolve($resource, $type);
if ($loader instanceof self && null !== $this->currentDir) {
$resource = $loader->getLocator()->locate($resource, $this->currentDir, false);
}
$resources = \is_array($resource) ? $resource : [$resource];
for ($i = 0; $i < $resourcesCount = \count($resources); ++$i) {
if (isset(self::$loading[$resources[$i]])) {
if ($i == $resourcesCount - 1) {
throw new FileLoaderImportCircularReferenceException(array_keys(self::$loading));
}
} else {
$resource = $resources[$i];
break;
}
}
self::$loading[$resource] = true;
try {
$ret = $loader->load($resource, $type);
} finally {
unset(self::$loading[$resource]);
}
return $ret;
} catch (FileLoaderImportCircularReferenceException $e) {
throw $e;
} catch (\Exception $e) {
if (!$ignoreErrors) {
// prevent embedded imports from nesting multiple exceptions
if ($e instanceof LoaderLoadException) {
throw $e;
}
throw new LoaderLoadException($resource, $sourceResource, null, $e, $type);
}
}
return null;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Loader;
/**
* LoaderResolver selects a loader for a given resource.
*
* A resource can be anything (e.g. a full path to a config file or a Closure).
* Each loader determines whether it can load a resource and how.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class LoaderResolver implements LoaderResolverInterface
{
/**
* @var LoaderInterface[] An array of LoaderInterface objects
*/
private $loaders = [];
/**
* @param LoaderInterface[] $loaders An array of loaders
*/
public function __construct(array $loaders = [])
{
foreach ($loaders as $loader) {
$this->addLoader($loader);
}
}
/**
* {@inheritdoc}
*/
public function resolve($resource, string $type = null)
{
foreach ($this->loaders as $loader) {
if ($loader->supports($resource, $type)) {
return $loader;
}
}
return false;
}
public function addLoader(LoaderInterface $loader)
{
$this->loaders[] = $loader;
$loader->setResolver($this);
}
/**
* Returns the registered loaders.
*
* @return LoaderInterface[] An array of LoaderInterface instances
*/
public function getLoaders()
{
return $this->loaders;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Loader;
use Symfony\Component\Config\Exception\LoaderLoadException;
/**
* DelegatingLoader delegates loading to other loaders using a loader resolver.
*
* This loader acts as an array of LoaderInterface objects - each having
* a chance to load a given resource (handled by the resolver)
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class DelegatingLoader extends Loader
{
public function __construct(LoaderResolverInterface $resolver)
{
$this->resolver = $resolver;
}
/**
* {@inheritdoc}
*/
public function load($resource, string $type = null)
{
if (false === $loader = $this->resolver->resolve($resource, $type)) {
throw new LoaderLoadException($resource, null, null, null, $type);
}
return $loader->load($resource, $type);
}
/**
* {@inheritdoc}
*/
public function supports($resource, string $type = null)
{
return false !== $this->resolver->resolve($resource, $type);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Loader;
/**
* GlobFileLoader loads files from a glob pattern.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class GlobFileLoader extends FileLoader
{
/**
* {@inheritdoc}
*/
public function load($resource, string $type = null)
{
return $this->import($resource);
}
/**
* {@inheritdoc}
*/
public function supports($resource, string $type = null)
{
return 'glob' === $type;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Loader;
/**
* LoaderResolverInterface selects a loader for a given resource.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
interface LoaderResolverInterface
{
/**
* Returns a loader able to load the resource.
*
* @param mixed $resource A resource
* @param string|null $type The resource type or null if unknown
*
* @return LoaderInterface|false The loader or false if none is able to load the resource
*/
public function resolve($resource, string $type = null);
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config;
use Symfony\Component\Config\Resource\ResourceInterface;
/**
* Interface for ConfigCache.
*
* @author Matthias Pigulla <mp@webfactory.de>
*/
interface ConfigCacheInterface
{
/**
* Gets the cache file path.
*
* @return string The cache file path
*/
public function getPath();
/**
* Checks if the cache is still fresh.
*
* This check should take the metadata passed to the write() method into consideration.
*
* @return bool Whether the cache is still fresh
*/
public function isFresh();
/**
* Writes the given content into the cache file. Metadata will be stored
* independently and can be used to check cache freshness at a later time.
*
* @param string $content The content to write into the cache
* @param ResourceInterface[]|null $metadata An array of ResourceInterface instances
*
* @throws \RuntimeException When the cache file cannot be written
*/
public function write(string $content, array $metadata = null);
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config;
use Symfony\Component\Config\Exception\FileLocatorFileNotFoundException;
/**
* @author Fabien Potencier <fabien@symfony.com>
*/
interface FileLocatorInterface
{
/**
* Returns a full path for a given file name.
*
* @param string $name The file name to locate
* @param string|null $currentPath The current path
* @param bool $first Whether to return the first occurrence or an array of filenames
*
* @return string|array The full path to the file or an array of file paths
*
* @throws \InvalidArgumentException If $name is empty
* @throws FileLocatorFileNotFoundException If a file is not found
*/
public function locate(string $name, string $currentPath = null, bool $first = true);
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config;
/**
* A ConfigCacheFactory implementation that validates the
* cache with an arbitrary set of ResourceCheckers.
*
* @author Matthias Pigulla <mp@webfactory.de>
*/
class ResourceCheckerConfigCacheFactory implements ConfigCacheFactoryInterface
{
private $resourceCheckers = [];
/**
* @param iterable|ResourceCheckerInterface[] $resourceCheckers
*/
public function __construct(iterable $resourceCheckers = [])
{
$this->resourceCheckers = $resourceCheckers;
}
/**
* {@inheritdoc}
*/
public function cache(string $file, callable $callable)
{
$cache = new ResourceCheckerConfigCache($file, $this->resourceCheckers);
if (!$cache->isFresh()) {
$callable($cache);
}
return $cache;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config;
use Symfony\Component\Config\Resource\ResourceInterface;
/**
* Interface for ResourceCheckers.
*
* When a ResourceCheckerConfigCache instance is checked for freshness, all its associated
* metadata resources are passed to ResourceCheckers. The ResourceCheckers
* can then inspect the resources and decide whether the cache can be considered
* fresh or not.
*
* @author Matthias Pigulla <mp@webfactory.de>
* @author Benjamin Klotz <bk@webfactory.de>
*/
interface ResourceCheckerInterface
{
/**
* Queries the ResourceChecker whether it can validate a given
* resource or not.
*
* @return bool True if the ResourceChecker can handle this resource type, false if not
*/
public function supports(ResourceInterface $metadata);
/**
* Validates the resource.
*
* @param int $timestamp The timestamp at which the cache associated with this resource was created
*
* @return bool True if the resource has not changed since the given timestamp, false otherwise
*/
public function isFresh(ResourceInterface $resource, int $timestamp);
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config;
/**
* Interface for a ConfigCache factory. This factory creates
* an instance of ConfigCacheInterface and initializes the
* cache if necessary.
*
* @author Matthias Pigulla <mp@webfactory.de>
*/
interface ConfigCacheFactoryInterface
{
/**
* Creates a cache instance and (re-)initializes it if necessary.
*
* @param string $file The absolute cache file path
* @param callable $callable The callable to be executed when the cache needs to be filled (i. e. is not fresh). The cache will be passed as the only parameter to this callback
*
* @return ConfigCacheInterface The cache instance
*/
public function cache(string $file, callable $callable);
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config;
/**
* Basic implementation of ConfigCacheFactoryInterface that
* creates an instance of the default ConfigCache.
*
* This factory and/or cache <em>do not</em> support cache validation
* by means of ResourceChecker instances (that is, service-based).
*
* @author Matthias Pigulla <mp@webfactory.de>
*/
class ConfigCacheFactory implements ConfigCacheFactoryInterface
{
private $debug;
/**
* @param bool $debug The debug flag to pass to ConfigCache
*/
public function __construct(bool $debug)
{
$this->debug = $debug;
}
/**
* {@inheritdoc}
*/
public function cache(string $file, callable $callback)
{
$cache = new ConfigCache($file, $this->debug);
if (!$cache->isFresh()) {
$callback($cache);
}
return $cache;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Resource;
/**
* FileExistenceResource represents a resource stored on the filesystem.
* Freshness is only evaluated against resource creation or deletion.
*
* The resource can be a file or a directory.
*
* @author Charles-Henri Bruyand <charleshenri.bruyand@gmail.com>
*
* @final
*/
class FileExistenceResource implements SelfCheckingResourceInterface
{
private $resource;
private $exists;
/**
* @param string $resource The file path to the resource
*/
public function __construct(string $resource)
{
$this->resource = $resource;
$this->exists = file_exists($resource);
}
/**
* {@inheritdoc}
*/
public function __toString(): string
{
return $this->resource;
}
/**
* @return string The file path to the resource
*/
public function getResource(): string
{
return $this->resource;
}
/**
* {@inheritdoc}
*/
public function isFresh(int $timestamp): bool
{
return file_exists($this->resource) === $this->exists;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Resource;
/**
* ClassExistenceResource represents a class existence.
* Freshness is only evaluated against resource existence.
*
* The resource must be a fully-qualified class name.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @final
*/
class ClassExistenceResource implements SelfCheckingResourceInterface
{
private $resource;
private $exists;
private static $autoloadLevel = 0;
private static $autoloadedClass;
private static $existsCache = [];
/**
* @param string $resource The fully-qualified class name
* @param bool|null $exists Boolean when the existency check has already been done
*/
public function __construct(string $resource, bool $exists = null)
{
$this->resource = $resource;
if (null !== $exists) {
$this->exists = [(bool) $exists, null];
}
}
/**
* {@inheritdoc}
*/
public function __toString(): string
{
return $this->resource;
}
/**
* @return string The file path to the resource
*/
public function getResource(): string
{
return $this->resource;
}
/**
* {@inheritdoc}
*
* @throws \ReflectionException when a parent class/interface/trait is not found
*/
public function isFresh(int $timestamp): bool
{
$loaded = class_exists($this->resource, false) || interface_exists($this->resource, false) || trait_exists($this->resource, false);
if (null !== $exists = &self::$existsCache[$this->resource]) {
if ($loaded) {
$exists = [true, null];
} elseif (0 >= $timestamp && !$exists[0] && null !== $exists[1]) {
throw new \ReflectionException($exists[1]);
}
} elseif ([false, null] === $exists = [$loaded, null]) {
if (!self::$autoloadLevel++) {
spl_autoload_register(__CLASS__.'::throwOnRequiredClass');
}
$autoloadedClass = self::$autoloadedClass;
self::$autoloadedClass = ltrim($this->resource, '\\');
try {
$exists[0] = class_exists($this->resource) || interface_exists($this->resource, false) || trait_exists($this->resource, false);
} catch (\Exception $e) {
$exists[1] = $e->getMessage();
try {
self::throwOnRequiredClass($this->resource, $e);
} catch (\ReflectionException $e) {
if (0 >= $timestamp) {
throw $e;
}
}
} catch (\Throwable $e) {
$exists[1] = $e->getMessage();
throw $e;
} finally {
self::$autoloadedClass = $autoloadedClass;
if (!--self::$autoloadLevel) {
spl_autoload_unregister(__CLASS__.'::throwOnRequiredClass');
}
}
}
if (null === $this->exists) {
$this->exists = $exists;
}
return $this->exists[0] xor !$exists[0];
}
/**
* @internal
*/
public function __sleep(): array
{
if (null === $this->exists) {
$this->isFresh(0);
}
return ['resource', 'exists'];
}
/**
* @internal
*/
public function __wakeup()
{
if (\is_bool($this->exists)) {
$this->exists = [$this->exists, null];
}
}
/**
* Throws a reflection exception when the passed class does not exist but is required.
*
* A class is considered "not required" when it's loaded as part of a "class_exists" or similar check.
*
* This function can be used as an autoload function to throw a reflection
* exception if the class was not found by previous autoload functions.
*
* A previous exception can be passed. In this case, the class is considered as being
* required totally, so if it doesn't exist, a reflection exception is always thrown.
* If it exists, the previous exception is rethrown.
*
* @throws \ReflectionException
*
* @internal
*/
public static function throwOnRequiredClass(string $class, \Exception $previous = null)
{
// If the passed class is the resource being checked, we shouldn't throw.
if (null === $previous && self::$autoloadedClass === $class) {
return;
}
if (class_exists($class, false) || interface_exists($class, false) || trait_exists($class, false)) {
if (null !== $previous) {
throw $previous;
}
return;
}
if ($previous instanceof \ReflectionException) {
throw $previous;
}
$message = sprintf('Class "%s" not found.', $class);
if (self::$autoloadedClass !== $class) {
$message = substr_replace($message, sprintf(' while loading "%s"', self::$autoloadedClass), -1, 0);
}
if (null !== $previous) {
$message = $previous->getMessage();
}
$e = new \ReflectionException($message, 0, $previous);
if (null !== $previous) {
throw $e;
}
$trace = debug_backtrace();
$autoloadFrame = [
'function' => 'spl_autoload_call',
'args' => [$class],
];
if (\PHP_VERSION_ID >= 80000 && isset($trace[1])) {
$callerFrame = $trace[1];
$i = 2;
} elseif (false !== $i = array_search($autoloadFrame, $trace, true)) {
$callerFrame = $trace[++$i];
} else {
throw $e;
}
if (isset($callerFrame['function']) && !isset($callerFrame['class'])) {
switch ($callerFrame['function']) {
case 'get_class_methods':
case 'get_class_vars':
case 'get_parent_class':
case 'is_a':
case 'is_subclass_of':
case 'class_exists':
case 'class_implements':
case 'class_parents':
case 'trait_exists':
case 'defined':
case 'interface_exists':
case 'method_exists':
case 'property_exists':
case 'is_callable':
return;
}
$props = [
'file' => isset($callerFrame['file']) ? $callerFrame['file'] : null,
'line' => isset($callerFrame['line']) ? $callerFrame['line'] : null,
'trace' => \array_slice($trace, 1 + $i),
];
foreach ($props as $p => $v) {
if (null !== $v) {
$r = new \ReflectionProperty('Exception', $p);
$r->setAccessible(true);
$r->setValue($e, $v);
}
}
}
throw $e;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Resource;
/**
* Interface for Resources that can check for freshness autonomously,
* without special support from external services.
*
* @author Matthias Pigulla <mp@webfactory.de>
*/
interface SelfCheckingResourceInterface extends ResourceInterface
{
/**
* Returns true if the resource has not been updated since the given timestamp.
*
* @param int $timestamp The last time the resource was loaded
*
* @return bool True if the resource has not been updated, false otherwise
*/
public function isFresh(int $timestamp);
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Resource;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Messenger\Handler\MessageSubscriberInterface;
use Symfony\Contracts\Service\ServiceSubscriberInterface;
/**
* @author Nicolas Grekas <p@tchwork.com>
*
* @final
*/
class ReflectionClassResource implements SelfCheckingResourceInterface
{
private $files = [];
private $className;
private $classReflector;
private $excludedVendors = [];
private $hash;
public function __construct(\ReflectionClass $classReflector, array $excludedVendors = [])
{
$this->className = $classReflector->name;
$this->classReflector = $classReflector;
$this->excludedVendors = $excludedVendors;
}
/**
* {@inheritdoc}
*/
public function isFresh(int $timestamp): bool
{
if (null === $this->hash) {
$this->hash = $this->computeHash();
$this->loadFiles($this->classReflector);
}
foreach ($this->files as $file => $v) {
if (false === $filemtime = @filemtime($file)) {
return false;
}
if ($filemtime > $timestamp) {
return $this->hash === $this->computeHash();
}
}
return true;
}
public function __toString(): string
{
return 'reflection.'.$this->className;
}
/**
* @internal
*/
public function __sleep(): array
{
if (null === $this->hash) {
$this->hash = $this->computeHash();
$this->loadFiles($this->classReflector);
}
return ['files', 'className', 'hash'];
}
private function loadFiles(\ReflectionClass $class)
{
foreach ($class->getInterfaces() as $v) {
$this->loadFiles($v);
}
do {
$file = $class->getFileName();
if (false !== $file && is_file($file)) {
foreach ($this->excludedVendors as $vendor) {
if (0 === strpos($file, $vendor) && false !== strpbrk(substr($file, \strlen($vendor), 1), '/'.\DIRECTORY_SEPARATOR)) {
$file = false;
break;
}
}
if ($file) {
$this->files[$file] = null;
}
}
foreach ($class->getTraits() as $v) {
$this->loadFiles($v);
}
} while ($class = $class->getParentClass());
}
private function computeHash(): string
{
if (null === $this->classReflector) {
try {
$this->classReflector = new \ReflectionClass($this->className);
} catch (\ReflectionException $e) {
// the class does not exist anymore
return false;
}
}
$hash = hash_init('md5');
foreach ($this->generateSignature($this->classReflector) as $info) {
hash_update($hash, $info);
}
return hash_final($hash);
}
private function generateSignature(\ReflectionClass $class): iterable
{
yield $class->getDocComment();
yield (int) $class->isFinal();
yield (int) $class->isAbstract();
if ($class->isTrait()) {
yield print_r(class_uses($class->name), true);
} else {
yield print_r(class_parents($class->name), true);
yield print_r(class_implements($class->name), true);
yield print_r($class->getConstants(), true);
}
if (!$class->isInterface()) {
$defaults = $class->getDefaultProperties();
foreach ($class->getProperties(\ReflectionProperty::IS_PUBLIC | \ReflectionProperty::IS_PROTECTED) as $p) {
yield $p->getDocComment();
yield $p->isDefault() ? '<default>' : '';
yield $p->isPublic() ? 'public' : 'protected';
yield $p->isStatic() ? 'static' : '';
yield '$'.$p->name;
yield print_r(isset($defaults[$p->name]) && !\is_object($defaults[$p->name]) ? $defaults[$p->name] : null, true);
}
}
foreach ($class->getMethods(\ReflectionMethod::IS_PUBLIC | \ReflectionMethod::IS_PROTECTED) as $m) {
$defaults = [];
$parametersWithUndefinedConstants = [];
foreach ($m->getParameters() as $p) {
if (!$p->isDefaultValueAvailable()) {
$defaults[$p->name] = null;
continue;
}
if (!$p->isDefaultValueConstant() || \defined($p->getDefaultValueConstantName())) {
$defaults[$p->name] = $p->getDefaultValue();
continue;
}
$defaults[$p->name] = $p->getDefaultValueConstantName();
$parametersWithUndefinedConstants[$p->name] = true;
}
if (!$parametersWithUndefinedConstants) {
yield preg_replace('/^ @@.*/m', '', $m);
} else {
$t = $m->getReturnType();
$stack = [
$m->getDocComment(),
$m->getName(),
$m->isAbstract(),
$m->isFinal(),
$m->isStatic(),
$m->isPublic(),
$m->isPrivate(),
$m->isProtected(),
$m->returnsReference(),
$t instanceof \ReflectionNamedType ? ((string) $t->allowsNull()).$t->getName() : (string) $t,
];
foreach ($m->getParameters() as $p) {
if (!isset($parametersWithUndefinedConstants[$p->name])) {
$stack[] = (string) $p;
} else {
$t = $p->getType();
$stack[] = $p->isOptional();
$stack[] = $t instanceof \ReflectionNamedType ? ((string) $t->allowsNull()).$t->getName() : (string) $t;
$stack[] = $p->isPassedByReference();
$stack[] = $p->isVariadic();
$stack[] = $p->getName();
}
}
yield implode(',', $stack);
}
yield print_r($defaults, true);
}
if ($class->isAbstract() || $class->isInterface() || $class->isTrait()) {
return;
}
if (interface_exists(EventSubscriberInterface::class, false) && $class->isSubclassOf(EventSubscriberInterface::class)) {
yield EventSubscriberInterface::class;
yield print_r($class->name::getSubscribedEvents(), true);
}
if (interface_exists(MessageSubscriberInterface::class, false) && $class->isSubclassOf(MessageSubscriberInterface::class)) {
yield MessageSubscriberInterface::class;
foreach ($class->name::getHandledMessages() as $key => $value) {
yield $key.print_r($value, true);
}
}
if (interface_exists(ServiceSubscriberInterface::class, false) && $class->isSubclassOf(ServiceSubscriberInterface::class)) {
yield ServiceSubscriberInterface::class;
yield print_r($class->name::getSubscribedServices(), true);
}
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Resource;
/**
* DirectoryResource represents a resources stored in a subdirectory tree.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @final
*/
class DirectoryResource implements SelfCheckingResourceInterface
{
private $resource;
private $pattern;
/**
* @param string $resource The file path to the resource
* @param string|null $pattern A pattern to restrict monitored files
*
* @throws \InvalidArgumentException
*/
public function __construct(string $resource, string $pattern = null)
{
$this->resource = realpath($resource) ?: (file_exists($resource) ? $resource : false);
$this->pattern = $pattern;
if (false === $this->resource || !is_dir($this->resource)) {
throw new \InvalidArgumentException(sprintf('The directory "%s" does not exist.', $resource));
}
}
/**
* {@inheritdoc}
*/
public function __toString(): string
{
return md5(serialize([$this->resource, $this->pattern]));
}
/**
* @return string The file path to the resource
*/
public function getResource(): string
{
return $this->resource;
}
/**
* Returns the pattern to restrict monitored files.
*/
public function getPattern(): ?string
{
return $this->pattern;
}
/**
* {@inheritdoc}
*/
public function isFresh(int $timestamp): bool
{
if (!is_dir($this->resource)) {
return false;
}
if ($timestamp < filemtime($this->resource)) {
return false;
}
foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->resource), \RecursiveIteratorIterator::SELF_FIRST) as $file) {
// if regex filtering is enabled only check matching files
if ($this->pattern && $file->isFile() && !preg_match($this->pattern, $file->getBasename())) {
continue;
}
// always monitor directories for changes, except the .. entries
// (otherwise deleted files wouldn't get detected)
if ($file->isDir() && '/..' === substr($file, -3)) {
continue;
}
// for broken links
try {
$fileMTime = $file->getMTime();
} catch (\RuntimeException $e) {
continue;
}
// early return if a file's mtime exceeds the passed timestamp
if ($timestamp < $fileMTime) {
return false;
}
}
return true;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Resource;
/**
* FileResource represents a resource stored on the filesystem.
*
* The resource can be a file or a directory.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @final
*/
class FileResource implements SelfCheckingResourceInterface
{
/**
* @var string|false
*/
private $resource;
/**
* @param string $resource The file path to the resource
*
* @throws \InvalidArgumentException
*/
public function __construct(string $resource)
{
$this->resource = realpath($resource) ?: (file_exists($resource) ? $resource : false);
if (false === $this->resource) {
throw new \InvalidArgumentException(sprintf('The file "%s" does not exist.', $resource));
}
}
/**
* {@inheritdoc}
*/
public function __toString(): string
{
return $this->resource;
}
/**
* @return string The canonicalized, absolute path to the resource
*/
public function getResource(): string
{
return $this->resource;
}
/**
* {@inheritdoc}
*/
public function isFresh(int $timestamp): bool
{
return false !== ($filemtime = @filemtime($this->resource)) && $filemtime <= $timestamp;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Resource;
/**
* ResourceInterface is the interface that must be implemented by all Resource classes.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
interface ResourceInterface
{
/**
* Returns a string representation of the Resource.
*
* This method is necessary to allow for resource de-duplication, for example by means
* of array_unique(). The string returned need not have a particular meaning, but has
* to be identical for different ResourceInterface instances referring to the same
* resource; and it should be unlikely to collide with that of other, unrelated
* resource instances.
*
* @return string A string representation unique to the underlying Resource
*/
public function __toString();
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Resource;
use Symfony\Component\Config\ResourceCheckerInterface;
/**
* Resource checker for instances of SelfCheckingResourceInterface.
*
* As these resources perform the actual check themselves, we can provide
* this class as a standard way of validating them.
*
* @author Matthias Pigulla <mp@webfactory.de>
*/
class SelfCheckingResourceChecker implements ResourceCheckerInterface
{
public function supports(ResourceInterface $metadata)
{
return $metadata instanceof SelfCheckingResourceInterface;
}
public function isFresh(ResourceInterface $resource, int $timestamp)
{
/* @var SelfCheckingResourceInterface $resource */
return $resource->isFresh($timestamp);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Resource;
/**
* ComposerResource tracks the PHP version and Composer dependencies.
*
* @author Nicolas Grekas <p@tchwork.com>
*
* @final
*/
class ComposerResource implements SelfCheckingResourceInterface
{
private $vendors;
private static $runtimeVendors;
public function __construct()
{
self::refresh();
$this->vendors = self::$runtimeVendors;
}
public function getVendors(): array
{
return array_keys($this->vendors);
}
/**
* {@inheritdoc}
*/
public function __toString(): string
{
return __CLASS__;
}
/**
* {@inheritdoc}
*/
public function isFresh(int $timestamp): bool
{
self::refresh();
return array_values(self::$runtimeVendors) === array_values($this->vendors);
}
private static function refresh()
{
self::$runtimeVendors = [];
foreach (get_declared_classes() as $class) {
if ('C' === $class[0] && 0 === strpos($class, 'ComposerAutoloaderInit')) {
$r = new \ReflectionClass($class);
$v = \dirname($r->getFileName(), 2);
if (is_file($v.'/composer/installed.json')) {
self::$runtimeVendors[$v] = @filemtime($v.'/composer/installed.json');
}
}
}
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Resource;
use Symfony\Component\Finder\Finder;
use Symfony\Component\Finder\Glob;
/**
* GlobResource represents a set of resources stored on the filesystem.
*
* Only existence/removal is tracked (not mtimes.)
*
* @author Nicolas Grekas <p@tchwork.com>
*
* @final
*/
class GlobResource implements \IteratorAggregate, SelfCheckingResourceInterface
{
private $prefix;
private $pattern;
private $recursive;
private $hash;
private $forExclusion;
private $excludedPrefixes;
private $globBrace;
/**
* @param string $prefix A directory prefix
* @param string $pattern A glob pattern
* @param bool $recursive Whether directories should be scanned recursively or not
*
* @throws \InvalidArgumentException
*/
public function __construct(string $prefix, string $pattern, bool $recursive, bool $forExclusion = false, array $excludedPrefixes = [])
{
ksort($excludedPrefixes);
$this->prefix = realpath($prefix) ?: (file_exists($prefix) ? $prefix : false);
$this->pattern = $pattern;
$this->recursive = $recursive;
$this->forExclusion = $forExclusion;
$this->excludedPrefixes = $excludedPrefixes;
$this->globBrace = \defined('GLOB_BRACE') ? \GLOB_BRACE : 0;
if (false === $this->prefix) {
throw new \InvalidArgumentException(sprintf('The path "%s" does not exist.', $prefix));
}
}
public function getPrefix(): string
{
return $this->prefix;
}
/**
* {@inheritdoc}
*/
public function __toString(): string
{
return 'glob.'.$this->prefix.(int) $this->recursive.$this->pattern.(int) $this->forExclusion.implode("\0", $this->excludedPrefixes);
}
/**
* {@inheritdoc}
*/
public function isFresh(int $timestamp): bool
{
$hash = $this->computeHash();
if (null === $this->hash) {
$this->hash = $hash;
}
return $this->hash === $hash;
}
/**
* @internal
*/
public function __sleep(): array
{
if (null === $this->hash) {
$this->hash = $this->computeHash();
}
return ['prefix', 'pattern', 'recursive', 'hash', 'forExclusion', 'excludedPrefixes'];
}
/**
* @internal
*/
public function __wakeup(): void
{
$this->globBrace = \defined('GLOB_BRACE') ? \GLOB_BRACE : 0;
}
public function getIterator(): \Traversable
{
if (!file_exists($this->prefix) || (!$this->recursive && '' === $this->pattern)) {
return;
}
$prefix = str_replace('\\', '/', $this->prefix);
$paths = null;
if (0 !== strpos($this->prefix, 'phar://') && false === strpos($this->pattern, '/**/')) {
if ($this->globBrace || false === strpos($this->pattern, '{')) {
$paths = glob($this->prefix.$this->pattern, \GLOB_NOSORT | $this->globBrace);
} elseif (false === strpos($this->pattern, '\\') || !preg_match('/\\\\[,{}]/', $this->pattern)) {
foreach ($this->expandGlob($this->pattern) as $p) {
$paths[] = glob($this->prefix.$p, \GLOB_NOSORT);
}
$paths = array_merge(...$paths);
}
}
if (null !== $paths) {
sort($paths);
foreach ($paths as $path) {
if ($this->excludedPrefixes) {
$normalizedPath = str_replace('\\', '/', $path);
do {
if (isset($this->excludedPrefixes[$dirPath = $normalizedPath])) {
continue 2;
}
} while ($prefix !== $dirPath && $dirPath !== $normalizedPath = \dirname($dirPath));
}
if (is_file($path)) {
yield $path => new \SplFileInfo($path);
}
if (!is_dir($path)) {
continue;
}
if ($this->forExclusion) {
yield $path => new \SplFileInfo($path);
continue;
}
if (!$this->recursive || isset($this->excludedPrefixes[str_replace('\\', '/', $path)])) {
continue;
}
$files = iterator_to_array(new \RecursiveIteratorIterator(
new \RecursiveCallbackFilterIterator(
new \RecursiveDirectoryIterator($path, \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS),
function (\SplFileInfo $file, $path) {
return !isset($this->excludedPrefixes[str_replace('\\', '/', $path)]) && '.' !== $file->getBasename()[0];
}
),
\RecursiveIteratorIterator::LEAVES_ONLY
));
uasort($files, 'strnatcmp');
foreach ($files as $path => $info) {
if ($info->isFile()) {
yield $path => $info;
}
}
}
return;
}
if (!class_exists(Finder::class)) {
throw new \LogicException(sprintf('Extended glob pattern "%s" cannot be used as the Finder component is not installed.', $this->pattern));
}
$finder = new Finder();
$regex = Glob::toRegex($this->pattern);
if ($this->recursive) {
$regex = substr_replace($regex, '(/|$)', -2, 1);
}
$prefixLen = \strlen($this->prefix);
foreach ($finder->followLinks()->sortByName()->in($this->prefix) as $path => $info) {
$normalizedPath = str_replace('\\', '/', $path);
if (!preg_match($regex, substr($normalizedPath, $prefixLen)) || !$info->isFile()) {
continue;
}
if ($this->excludedPrefixes) {
do {
if (isset($this->excludedPrefixes[$dirPath = $normalizedPath])) {
continue 2;
}
} while ($prefix !== $dirPath && $dirPath !== $normalizedPath = \dirname($dirPath));
}
yield $path => $info;
}
}
private function computeHash(): string
{
$hash = hash_init('md5');
foreach ($this->getIterator() as $path => $info) {
hash_update($hash, $path."\n");
}
return hash_final($hash);
}
private function expandGlob(string $pattern): array
{
$segments = preg_split('/\{([^{}]*+)\}/', $pattern, -1, \PREG_SPLIT_DELIM_CAPTURE);
$paths = [$segments[0]];
$patterns = [];
for ($i = 1; $i < \count($segments); $i += 2) {
$patterns = [];
foreach (explode(',', $segments[$i]) as $s) {
foreach ($paths as $p) {
$patterns[] = $p.$s.$segments[1 + $i];
}
}
$paths = $patterns;
}
$j = 0;
foreach ($patterns as $i => $p) {
if (false !== strpos($p, '{')) {
$p = $this->expandGlob($p);
array_splice($paths, $i + $j, 1, $p);
$j += \count($p) - 1;
}
}
return $paths;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config;
use Symfony\Component\Config\Resource\SelfCheckingResourceChecker;
/**
* ConfigCache caches arbitrary content in files on disk.
*
* When in debug mode, those metadata resources that implement
* \Symfony\Component\Config\Resource\SelfCheckingResourceInterface will
* be used to check cache freshness.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Matthias Pigulla <mp@webfactory.de>
*/
class ConfigCache extends ResourceCheckerConfigCache
{
private $debug;
/**
* @param string $file The absolute cache path
* @param bool $debug Whether debugging is enabled or not
*/
public function __construct(string $file, bool $debug)
{
$this->debug = $debug;
$checkers = [];
if (true === $this->debug) {
$checkers = [new SelfCheckingResourceChecker()];
}
parent::__construct($file, $checkers);
}
/**
* Checks if the cache is still fresh.
*
* This implementation always returns true when debug is off and the
* cache file exists.
*
* @return bool true if the cache is fresh, false otherwise
*/
public function isFresh()
{
if (!$this->debug && is_file($this->getPath())) {
return true;
}
return parent::isFresh();
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Command;
use Symfony\Component\Console\Helper\DescriptorHelper;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
* ListCommand displays the list of all available commands for the application.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class ListCommand extends Command
{
/**
* {@inheritdoc}
*/
protected function configure()
{
$this
->setName('list')
->setDefinition([
new InputArgument('namespace', InputArgument::OPTIONAL, 'The namespace name'),
new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command list'),
new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt'),
])
->setDescription('Lists commands')
->setHelp(<<<'EOF'
The <info>%command.name%</info> command lists all commands:
<info>%command.full_name%</info>
You can also display the commands for a specific namespace:
<info>%command.full_name% test</info>
You can also output the information in other formats by using the <comment>--format</comment> option:
<info>%command.full_name% --format=xml</info>
It's also possible to get raw list of commands (useful for embedding command runner):
<info>%command.full_name% --raw</info>
EOF
)
;
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$helper = new DescriptorHelper();
$helper->describe($output, $this->getApplication(), [
'format' => $input->getOption('format'),
'raw_text' => $input->getOption('raw'),
'namespace' => $input->getArgument('namespace'),
]);
return 0;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Command;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Exception\ExceptionInterface;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\Console\Helper\HelperSet;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Base class for all commands.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class Command
{
public const SUCCESS = 0;
public const FAILURE = 1;
/**
* @var string|null The default command name
*/
protected static $defaultName;
private $application;
private $name;
private $processTitle;
private $aliases = [];
private $definition;
private $hidden = false;
private $help = '';
private $description = '';
private $fullDefinition;
private $ignoreValidationErrors = false;
private $code;
private $synopsis = [];
private $usages = [];
private $helperSet;
/**
* @return string|null The default command name or null when no default name is set
*/
public static function getDefaultName()
{
$class = static::class;
$r = new \ReflectionProperty($class, 'defaultName');
return $class === $r->class ? static::$defaultName : null;
}
/**
* @param string|null $name The name of the command; passing null means it must be set in configure()
*
* @throws LogicException When the command name is empty
*/
public function __construct(string $name = null)
{
$this->definition = new InputDefinition();
if (null !== $name || null !== $name = static::getDefaultName()) {
$this->setName($name);
}
$this->configure();
}
/**
* Ignores validation errors.
*
* This is mainly useful for the help command.
*/
public function ignoreValidationErrors()
{
$this->ignoreValidationErrors = true;
}
public function setApplication(Application $application = null)
{
$this->application = $application;
if ($application) {
$this->setHelperSet($application->getHelperSet());
} else {
$this->helperSet = null;
}
$this->fullDefinition = null;
}
public function setHelperSet(HelperSet $helperSet)
{
$this->helperSet = $helperSet;
}
/**
* Gets the helper set.
*
* @return HelperSet|null A HelperSet instance
*/
public function getHelperSet()
{
return $this->helperSet;
}
/**
* Gets the application instance for this command.
*
* @return Application|null An Application instance
*/
public function getApplication()
{
return $this->application;
}
/**
* Checks whether the command is enabled or not in the current environment.
*
* Override this to check for x or y and return false if the command can not
* run properly under the current conditions.
*
* @return bool
*/
public function isEnabled()
{
return true;
}
/**
* Configures the current command.
*/
protected function configure()
{
}
/**
* Executes the current command.
*
* This method is not abstract because you can use this class
* as a concrete class. In this case, instead of defining the
* execute() method, you set the code to execute by passing
* a Closure to the setCode() method.
*
* @return int 0 if everything went fine, or an exit code
*
* @throws LogicException When this abstract method is not implemented
*
* @see setCode()
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
throw new LogicException('You must override the execute() method in the concrete command class.');
}
/**
* Interacts with the user.
*
* This method is executed before the InputDefinition is validated.
* This means that this is the only place where the command can
* interactively ask for values of missing required arguments.
*/
protected function interact(InputInterface $input, OutputInterface $output)
{
}
/**
* Initializes the command after the input has been bound and before the input
* is validated.
*
* This is mainly useful when a lot of commands extends one main command
* where some things need to be initialized based on the input arguments and options.
*
* @see InputInterface::bind()
* @see InputInterface::validate()
*/
protected function initialize(InputInterface $input, OutputInterface $output)
{
}
/**
* Runs the command.
*
* The code to execute is either defined directly with the
* setCode() method or by overriding the execute() method
* in a sub-class.
*
* @return int The command exit code
*
* @throws \Exception When binding input fails. Bypass this by calling {@link ignoreValidationErrors()}.
*
* @see setCode()
* @see execute()
*/
public function run(InputInterface $input, OutputInterface $output)
{
// add the application arguments and options
$this->mergeApplicationDefinition();
// bind the input against the command specific arguments/options
try {
$input->bind($this->getDefinition());
} catch (ExceptionInterface $e) {
if (!$this->ignoreValidationErrors) {
throw $e;
}
}
$this->initialize($input, $output);
if (null !== $this->processTitle) {
if (\function_exists('cli_set_process_title')) {
if (!@cli_set_process_title($this->processTitle)) {
if ('Darwin' === \PHP_OS) {
$output->writeln('<comment>Running "cli_set_process_title" as an unprivileged user is not supported on MacOS.</comment>', OutputInterface::VERBOSITY_VERY_VERBOSE);
} else {
cli_set_process_title($this->processTitle);
}
}
} elseif (\function_exists('setproctitle')) {
setproctitle($this->processTitle);
} elseif (OutputInterface::VERBOSITY_VERY_VERBOSE === $output->getVerbosity()) {
$output->writeln('<comment>Install the proctitle PECL to be able to change the process title.</comment>');
}
}
if ($input->isInteractive()) {
$this->interact($input, $output);
}
// The command name argument is often omitted when a command is executed directly with its run() method.
// It would fail the validation if we didn't make sure the command argument is present,
// since it's required by the application.
if ($input->hasArgument('command') && null === $input->getArgument('command')) {
$input->setArgument('command', $this->getName());
}
$input->validate();
if ($this->code) {
$statusCode = ($this->code)($input, $output);
} else {
$statusCode = $this->execute($input, $output);
if (!\is_int($statusCode)) {
throw new \TypeError(sprintf('Return value of "%s::execute()" must be of the type int, "%s" returned.', static::class, get_debug_type($statusCode)));
}
}
return is_numeric($statusCode) ? (int) $statusCode : 0;
}
/**
* Sets the code to execute when running this command.
*
* If this method is used, it overrides the code defined
* in the execute() method.
*
* @param callable $code A callable(InputInterface $input, OutputInterface $output)
*
* @return $this
*
* @throws InvalidArgumentException
*
* @see execute()
*/
public function setCode(callable $code)
{
if ($code instanceof \Closure) {
$r = new \ReflectionFunction($code);
if (null === $r->getClosureThis()) {
$code = \Closure::bind($code, $this);
}
}
$this->code = $code;
return $this;
}
/**
* Merges the application definition with the command definition.
*
* This method is not part of public API and should not be used directly.
*
* @param bool $mergeArgs Whether to merge or not the Application definition arguments to Command definition arguments
*/
public function mergeApplicationDefinition(bool $mergeArgs = true)
{
if (null === $this->application) {
return;
}
$this->fullDefinition = new InputDefinition();
$this->fullDefinition->setOptions($this->definition->getOptions());
$this->fullDefinition->addOptions($this->application->getDefinition()->getOptions());
if ($mergeArgs) {
$this->fullDefinition->setArguments($this->application->getDefinition()->getArguments());
$this->fullDefinition->addArguments($this->definition->getArguments());
} else {
$this->fullDefinition->setArguments($this->definition->getArguments());
}
}
/**
* Sets an array of argument and option instances.
*
* @param array|InputDefinition $definition An array of argument and option instances or a definition instance
*
* @return $this
*/
public function setDefinition($definition)
{
if ($definition instanceof InputDefinition) {
$this->definition = $definition;
} else {
$this->definition->setDefinition($definition);
}
$this->fullDefinition = null;
return $this;
}
/**
* Gets the InputDefinition attached to this Command.
*
* @return InputDefinition An InputDefinition instance
*/
public function getDefinition()
{
return $this->fullDefinition ?? $this->getNativeDefinition();
}
/**
* Gets the InputDefinition to be used to create representations of this Command.
*
* Can be overridden to provide the original command representation when it would otherwise
* be changed by merging with the application InputDefinition.
*
* This method is not part of public API and should not be used directly.
*
* @return InputDefinition An InputDefinition instance
*/
public function getNativeDefinition()
{
if (null === $this->definition) {
throw new LogicException(sprintf('Command class "%s" is not correctly initialized. You probably forgot to call the parent constructor.', static::class));
}
return $this->definition;
}
/**
* Adds an argument.
*
* @param int|null $mode The argument mode: InputArgument::REQUIRED or InputArgument::OPTIONAL
* @param string|string[]|null $default The default value (for InputArgument::OPTIONAL mode only)
*
* @throws InvalidArgumentException When argument mode is not valid
*
* @return $this
*/
public function addArgument(string $name, int $mode = null, string $description = '', $default = null)
{
$this->definition->addArgument(new InputArgument($name, $mode, $description, $default));
if (null !== $this->fullDefinition) {
$this->fullDefinition->addArgument(new InputArgument($name, $mode, $description, $default));
}
return $this;
}
/**
* Adds an option.
*
* @param string|array|null $shortcut The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts
* @param int|null $mode The option mode: One of the InputOption::VALUE_* constants
* @param string|string[]|int|bool|null $default The default value (must be null for InputOption::VALUE_NONE)
*
* @throws InvalidArgumentException If option mode is invalid or incompatible
*
* @return $this
*/
public function addOption(string $name, $shortcut = null, int $mode = null, string $description = '', $default = null)
{
$this->definition->addOption(new InputOption($name, $shortcut, $mode, $description, $default));
if (null !== $this->fullDefinition) {
$this->fullDefinition->addOption(new InputOption($name, $shortcut, $mode, $description, $default));
}
return $this;
}
/**
* Sets the name of the command.
*
* This method can set both the namespace and the name if
* you separate them by a colon (:)
*
* $command->setName('foo:bar');
*
* @return $this
*
* @throws InvalidArgumentException When the name is invalid
*/
public function setName(string $name)
{
$this->validateName($name);
$this->name = $name;
return $this;
}
/**
* Sets the process title of the command.
*
* This feature should be used only when creating a long process command,
* like a daemon.
*
* @return $this
*/
public function setProcessTitle(string $title)
{
$this->processTitle = $title;
return $this;
}
/**
* Returns the command name.
*
* @return string|null
*/
public function getName()
{
return $this->name;
}
/**
* @param bool $hidden Whether or not the command should be hidden from the list of commands
* The default value will be true in Symfony 6.0
*
* @return Command The current instance
*
* @final since Symfony 5.1
*/
public function setHidden(bool $hidden /*= true*/)
{
$this->hidden = $hidden;
return $this;
}
/**
* @return bool whether the command should be publicly shown or not
*/
public function isHidden()
{
return $this->hidden;
}
/**
* Sets the description for the command.
*
* @return $this
*/
public function setDescription(string $description)
{
$this->description = $description;
return $this;
}
/**
* Returns the description for the command.
*
* @return string The description for the command
*/
public function getDescription()
{
return $this->description;
}
/**
* Sets the help for the command.
*
* @return $this
*/
public function setHelp(string $help)
{
$this->help = $help;
return $this;
}
/**
* Returns the help for the command.
*
* @return string The help for the command
*/
public function getHelp()
{
return $this->help;
}
/**
* Returns the processed help for the command replacing the %command.name% and
* %command.full_name% patterns with the real values dynamically.
*
* @return string The processed help for the command
*/
public function getProcessedHelp()
{
$name = $this->name;
$isSingleCommand = $this->application && $this->application->isSingleCommand();
$placeholders = [
'%command.name%',
'%command.full_name%',
];
$replacements = [
$name,
$isSingleCommand ? $_SERVER['PHP_SELF'] : $_SERVER['PHP_SELF'].' '.$name,
];
return str_replace($placeholders, $replacements, $this->getHelp() ?: $this->getDescription());
}
/**
* Sets the aliases for the command.
*
* @param string[] $aliases An array of aliases for the command
*
* @return $this
*
* @throws InvalidArgumentException When an alias is invalid
*/
public function setAliases(iterable $aliases)
{
foreach ($aliases as $alias) {
$this->validateName($alias);
}
$this->aliases = $aliases;
return $this;
}
/**
* Returns the aliases for the command.
*
* @return array An array of aliases for the command
*/
public function getAliases()
{
return $this->aliases;
}
/**
* Returns the synopsis for the command.
*
* @param bool $short Whether to show the short version of the synopsis (with options folded) or not
*
* @return string The synopsis
*/
public function getSynopsis(bool $short = false)
{
$key = $short ? 'short' : 'long';
if (!isset($this->synopsis[$key])) {
$this->synopsis[$key] = trim(sprintf('%s %s', $this->name, $this->definition->getSynopsis($short)));
}
return $this->synopsis[$key];
}
/**
* Add a command usage example, it'll be prefixed with the command name.
*
* @return $this
*/
public function addUsage(string $usage)
{
if (0 !== strpos($usage, $this->name)) {
$usage = sprintf('%s %s', $this->name, $usage);
}
$this->usages[] = $usage;
return $this;
}
/**
* Returns alternative usages of the command.
*
* @return array
*/
public function getUsages()
{
return $this->usages;
}
/**
* Gets a helper instance by name.
*
* @return mixed The helper value
*
* @throws LogicException if no HelperSet is defined
* @throws InvalidArgumentException if the helper is not defined
*/
public function getHelper(string $name)
{
if (null === $this->helperSet) {
throw new LogicException(sprintf('Cannot retrieve helper "%s" because there is no HelperSet defined. Did you forget to add your command to the application or to set the application on the command using the setApplication() method? You can also set the HelperSet directly using the setHelperSet() method.', $name));
}
return $this->helperSet->get($name);
}
/**
* Validates a command name.
*
* It must be non-empty and parts can optionally be separated by ":".
*
* @throws InvalidArgumentException When the name is invalid
*/
private function validateName(string $name)
{
if (!preg_match('/^[^\:]++(\:[^\:]++)*$/', $name)) {
throw new InvalidArgumentException(sprintf('Command name "%s" is invalid.', $name));
}
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Command;
use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\Lock\Lock;
use Symfony\Component\Lock\LockFactory;
use Symfony\Component\Lock\Store\FlockStore;
use Symfony\Component\Lock\Store\SemaphoreStore;
/**
* Basic lock feature for commands.
*
* @author Geoffrey Brier <geoffrey.brier@gmail.com>
*/
trait LockableTrait
{
/** @var Lock */
private $lock;
/**
* Locks a command.
*/
private function lock(string $name = null, bool $blocking = false): bool
{
if (!class_exists(SemaphoreStore::class)) {
throw new LogicException('To enable the locking feature you must install the symfony/lock component.');
}
if (null !== $this->lock) {
throw new LogicException('A lock is already in place.');
}
if (SemaphoreStore::isSupported()) {
$store = new SemaphoreStore();
} else {
$store = new FlockStore();
}
$this->lock = (new LockFactory($store))->createLock($name ?: $this->getName());
if (!$this->lock->acquire($blocking)) {
$this->lock = null;
return false;
}
return true;
}
/**
* Releases the command lock if there is one.
*/
private function release()
{
if ($this->lock) {
$this->lock->release();
$this->lock = null;
}
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Command;
use Symfony\Component\Console\Helper\DescriptorHelper;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
* HelpCommand displays the help for a given command.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class HelpCommand extends Command
{
private $command;
/**
* {@inheritdoc}
*/
protected function configure()
{
$this->ignoreValidationErrors();
$this
->setName('help')
->setDefinition([
new InputArgument('command_name', InputArgument::OPTIONAL, 'The command name', 'help'),
new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt'),
new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command help'),
])
->setDescription('Displays help for a command')
->setHelp(<<<'EOF'
The <info>%command.name%</info> command displays help for a given command:
<info>%command.full_name% list</info>
You can also output the help in other formats by using the <comment>--format</comment> option:
<info>%command.full_name% --format=xml list</info>
To display the list of available commands, please use the <info>list</info> command.
EOF
)
;
}
public function setCommand(Command $command)
{
$this->command = $command;
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
if (null === $this->command) {
$this->command = $this->getApplication()->find($input->getArgument('command_name'));
}
$helper = new DescriptorHelper();
$helper->describe($output, $this->command, [
'format' => $input->getOption('format'),
'raw_text' => $input->getOption('raw'),
]);
$this->command = null;
return 0;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Command;
/**
* Interface for command reacting to signal.
*
* @author Grégoire Pineau <lyrixx@lyrix.info>
*/
interface SignalableCommandInterface
{
/**
* Returns the list of signals to subscribe.
*/
public function getSubscribedSignals(): array;
/**
* The method will be called when the application is signaled.
*/
public function handleSignal(int $signal): void;
}
Copyright (c) 2004-2020 Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Output;
use Symfony\Component\Console\Formatter\OutputFormatterInterface;
use Symfony\Component\Console\Helper\Helper;
use Symfony\Component\Console\Terminal;
/**
* @author Pierre du Plessis <pdples@gmail.com>
* @author Gabriel Ostrolucký <gabriel.ostrolucky@gmail.com>
*/
class ConsoleSectionOutput extends StreamOutput
{
private $content = [];
private $lines = 0;
private $sections;
private $terminal;
/**
* @param resource $stream
* @param ConsoleSectionOutput[] $sections
*/
public function __construct($stream, array &$sections, int $verbosity, bool $decorated, OutputFormatterInterface $formatter)
{
parent::__construct($stream, $verbosity, $decorated, $formatter);
array_unshift($sections, $this);
$this->sections = &$sections;
$this->terminal = new Terminal();
}
/**
* Clears previous output for this section.
*
* @param int $lines Number of lines to clear. If null, then the entire output of this section is cleared
*/
public function clear(int $lines = null)
{
if (empty($this->content) || !$this->isDecorated()) {
return;
}
if ($lines) {
array_splice($this->content, -($lines * 2)); // Multiply lines by 2 to cater for each new line added between content
} else {
$lines = $this->lines;
$this->content = [];
}
$this->lines -= $lines;
parent::doWrite($this->popStreamContentUntilCurrentSection($lines), false);
}
/**
* Overwrites the previous output with a new message.
*
* @param array|string $message
*/
public function overwrite($message)
{
$this->clear();
$this->writeln($message);
}
public function getContent(): string
{
return implode('', $this->content);
}
/**
* @internal
*/
public function addContent(string $input)
{
foreach (explode(\PHP_EOL, $input) as $lineContent) {
$this->lines += ceil($this->getDisplayLength($lineContent) / $this->terminal->getWidth()) ?: 1;
$this->content[] = $lineContent;
$this->content[] = \PHP_EOL;
}
}
/**
* {@inheritdoc}
*/
protected function doWrite($message, $newline)
{
if (!$this->isDecorated()) {
parent::doWrite($message, $newline);
return;
}
$erasedContent = $this->popStreamContentUntilCurrentSection();
$this->addContent($message);
parent::doWrite($message, true);
parent::doWrite($erasedContent, false);
}
/**
* At initial stage, cursor is at the end of stream output. This method makes cursor crawl upwards until it hits
* current section. Then it erases content it crawled through. Optionally, it erases part of current section too.
*/
private function popStreamContentUntilCurrentSection(int $numberOfLinesToClearFromCurrentSection = 0): string
{
$numberOfLinesToClear = $numberOfLinesToClearFromCurrentSection;
$erasedContent = [];
foreach ($this->sections as $section) {
if ($section === $this) {
break;
}
$numberOfLinesToClear += $section->lines;
$erasedContent[] = $section->getContent();
}
if ($numberOfLinesToClear > 0) {
// move cursor up n lines
parent::doWrite(sprintf("\x1b[%dA", $numberOfLinesToClear), false);
// erase to end of screen
parent::doWrite("\x1b[0J", false);
}
return implode('', array_reverse($erasedContent));
}
private function getDisplayLength(string $text): string
{
return Helper::strlenWithoutDecoration($this->getFormatter(), str_replace("\t", ' ', $text));
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Output;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Formatter\OutputFormatterInterface;
/**
* StreamOutput writes the output to a given stream.
*
* Usage:
*
* $output = new StreamOutput(fopen('php://stdout', 'w'));
*
* As `StreamOutput` can use any stream, you can also use a file:
*
* $output = new StreamOutput(fopen('/path/to/output.log', 'a', false));
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class StreamOutput extends Output
{
private $stream;
/**
* @param resource $stream A stream resource
* @param int $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface)
* @param bool|null $decorated Whether to decorate messages (null for auto-guessing)
* @param OutputFormatterInterface|null $formatter Output formatter instance (null to use default OutputFormatter)
*
* @throws InvalidArgumentException When first argument is not a real stream
*/
public function __construct($stream, int $verbosity = self::VERBOSITY_NORMAL, bool $decorated = null, OutputFormatterInterface $formatter = null)
{
if (!\is_resource($stream) || 'stream' !== get_resource_type($stream)) {
throw new InvalidArgumentException('The StreamOutput class needs a stream as its first argument.');
}
$this->stream = $stream;
if (null === $decorated) {
$decorated = $this->hasColorSupport();
}
parent::__construct($verbosity, $decorated, $formatter);
}
/**
* Gets the stream attached to this StreamOutput instance.
*
* @return resource A stream resource
*/
public function getStream()
{
return $this->stream;
}
/**
* {@inheritdoc}
*/
protected function doWrite(string $message, bool $newline)
{
if ($newline) {
$message .= \PHP_EOL;
}
@fwrite($this->stream, $message);
fflush($this->stream);
}
/**
* Returns true if the stream supports colorization.
*
* Colorization is disabled if not supported by the stream:
*
* This is tricky on Windows, because Cygwin, Msys2 etc emulate pseudo
* terminals via named pipes, so we can only check the environment.
*
* Reference: Composer\XdebugHandler\Process::supportsColor
* https://github.com/composer/xdebug-handler
*
* @return bool true if the stream supports colorization, false otherwise
*/
protected function hasColorSupport()
{
// Follow https://no-color.org/
if (isset($_SERVER['NO_COLOR']) || false !== getenv('NO_COLOR')) {
return false;
}
if ('Hyper' === getenv('TERM_PROGRAM')) {
return true;
}
if (\DIRECTORY_SEPARATOR === '\\') {
return (\function_exists('sapi_windows_vt100_support')
&& @sapi_windows_vt100_support($this->stream))
|| false !== getenv('ANSICON')
|| 'ON' === getenv('ConEmuANSI')
|| 'xterm' === getenv('TERM');
}
return stream_isatty($this->stream);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Output;
/**
* @author Jean-François Simon <contact@jfsimon.fr>
*/
class BufferedOutput extends Output
{
private $buffer = '';
/**
* Empties buffer and returns its content.
*
* @return string
*/
public function fetch()
{
$content = $this->buffer;
$this->buffer = '';
return $content;
}
/**
* {@inheritdoc}
*/
protected function doWrite(string $message, bool $newline)
{
$this->buffer .= $message;
if ($newline) {
$this->buffer .= \PHP_EOL;
}
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Output;
use Symfony\Component\Console\Formatter\OutputFormatterInterface;
/**
* ConsoleOutput is the default class for all CLI output. It uses STDOUT and STDERR.
*
* This class is a convenient wrapper around `StreamOutput` for both STDOUT and STDERR.
*
* $output = new ConsoleOutput();
*
* This is equivalent to:
*
* $output = new StreamOutput(fopen('php://stdout', 'w'));
* $stdErr = new StreamOutput(fopen('php://stderr', 'w'));
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class ConsoleOutput extends StreamOutput implements ConsoleOutputInterface
{
private $stderr;
private $consoleSectionOutputs = [];
/**
* @param int $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface)
* @param bool|null $decorated Whether to decorate messages (null for auto-guessing)
* @param OutputFormatterInterface|null $formatter Output formatter instance (null to use default OutputFormatter)
*/
public function __construct(int $verbosity = self::VERBOSITY_NORMAL, bool $decorated = null, OutputFormatterInterface $formatter = null)
{
parent::__construct($this->openOutputStream(), $verbosity, $decorated, $formatter);
if (null === $formatter) {
// for BC reasons, stdErr has it own Formatter only when user don't inject a specific formatter.
$this->stderr = new StreamOutput($this->openErrorStream(), $verbosity, $decorated);
return;
}
$actualDecorated = $this->isDecorated();
$this->stderr = new StreamOutput($this->openErrorStream(), $verbosity, $decorated, $this->getFormatter());
if (null === $decorated) {
$this->setDecorated($actualDecorated && $this->stderr->isDecorated());
}
}
/**
* Creates a new output section.
*/
public function section(): ConsoleSectionOutput
{
return new ConsoleSectionOutput($this->getStream(), $this->consoleSectionOutputs, $this->getVerbosity(), $this->isDecorated(), $this->getFormatter());
}
/**
* {@inheritdoc}
*/
public function setDecorated(bool $decorated)
{
parent::setDecorated($decorated);
$this->stderr->setDecorated($decorated);
}
/**
* {@inheritdoc}
*/
public function setFormatter(OutputFormatterInterface $formatter)
{
parent::setFormatter($formatter);
$this->stderr->setFormatter($formatter);
}
/**
* {@inheritdoc}
*/
public function setVerbosity(int $level)
{
parent::setVerbosity($level);
$this->stderr->setVerbosity($level);
}
/**
* {@inheritdoc}
*/
public function getErrorOutput()
{
return $this->stderr;
}
/**
* {@inheritdoc}
*/
public function setErrorOutput(OutputInterface $error)
{
$this->stderr = $error;
}
/**
* Returns true if current environment supports writing console output to
* STDOUT.
*
* @return bool
*/
protected function hasStdoutSupport()
{
return false === $this->isRunningOS400();
}
/**
* Returns true if current environment supports writing console output to
* STDERR.
*
* @return bool
*/
protected function hasStderrSupport()
{
return false === $this->isRunningOS400();
}
/**
* Checks if current executing environment is IBM iSeries (OS400), which
* doesn't properly convert character-encodings between ASCII to EBCDIC.
*/
private function isRunningOS400(): bool
{
$checks = [
\function_exists('php_uname') ? php_uname('s') : '',
getenv('OSTYPE'),
\PHP_OS,
];
return false !== stripos(implode(';', $checks), 'OS400');
}
/**
* @return resource
*/
private function openOutputStream()
{
if (!$this->hasStdoutSupport()) {
return fopen('php://output', 'w');
}
return @fopen('php://stdout', 'w') ?: fopen('php://output', 'w');
}
/**
* @return resource
*/
private function openErrorStream()
{
return fopen($this->hasStderrSupport() ? 'php://stderr' : 'php://output', 'w');
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Output;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Formatter\OutputFormatterInterface;
/**
* A BufferedOutput that keeps only the last N chars.
*
* @author Jérémy Derussé <jeremy@derusse.com>
*/
class TrimmedBufferOutput extends Output
{
private $maxLength;
private $buffer = '';
public function __construct(
int $maxLength,
?int $verbosity = self::VERBOSITY_NORMAL,
bool $decorated = false,
OutputFormatterInterface $formatter = null
) {
if ($maxLength <= 0) {
throw new InvalidArgumentException(sprintf('"%s()" expects a strictly positive maxLength. Got %d.', __METHOD__, $maxLength));
}
parent::__construct($verbosity, $decorated, $formatter);
$this->maxLength = $maxLength;
}
/**
* Empties buffer and returns its content.
*
* @return string
*/
public function fetch()
{
$content = $this->buffer;
$this->buffer = '';
return $content;
}
/**
* {@inheritdoc}
*/
protected function doWrite($message, $newline)
{
$this->buffer .= $message;
if ($newline) {
$this->buffer .= \PHP_EOL;
}
$this->buffer = substr($this->buffer, 0 - $this->maxLength);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Output;
use Symfony\Component\Console\Formatter\NullOutputFormatter;
use Symfony\Component\Console\Formatter\OutputFormatterInterface;
/**
* NullOutput suppresses all output.
*
* $output = new NullOutput();
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Tobias Schultze <http://tobion.de>
*/
class NullOutput implements OutputInterface
{
private $formatter;
/**
* {@inheritdoc}
*/
public function setFormatter(OutputFormatterInterface $formatter)
{
// do nothing
}
/**
* {@inheritdoc}
*/
public function getFormatter()
{
if ($this->formatter) {
return $this->formatter;
}
// to comply with the interface we must return a OutputFormatterInterface
return $this->formatter = new NullOutputFormatter();
}
/**
* {@inheritdoc}
*/
public function setDecorated(bool $decorated)
{
// do nothing
}
/**
* {@inheritdoc}
*/
public function isDecorated()
{
return false;
}
/**
* {@inheritdoc}
*/
public function setVerbosity(int $level)
{
// do nothing
}
/**
* {@inheritdoc}
*/
public function getVerbosity()
{
return self::VERBOSITY_QUIET;
}
/**
* {@inheritdoc}
*/
public function isQuiet()
{
return true;
}
/**
* {@inheritdoc}
*/
public function isVerbose()
{
return false;
}
/**
* {@inheritdoc}
*/
public function isVeryVerbose()
{
return false;
}
/**
* {@inheritdoc}
*/
public function isDebug()
{
return false;
}
/**
* {@inheritdoc}
*/
public function writeln($messages, int $options = self::OUTPUT_NORMAL)
{
// do nothing
}
/**
* {@inheritdoc}
*/
public function write($messages, bool $newline = false, int $options = self::OUTPUT_NORMAL)
{
// do nothing
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Output;
/**
* ConsoleOutputInterface is the interface implemented by ConsoleOutput class.
* This adds information about stderr and section output stream.
*
* @author Dariusz Górecki <darek.krk@gmail.com>
*/
interface ConsoleOutputInterface extends OutputInterface
{
/**
* Gets the OutputInterface for errors.
*
* @return OutputInterface
*/
public function getErrorOutput();
public function setErrorOutput(OutputInterface $error);
public function section(): ConsoleSectionOutput;
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Output;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Formatter\OutputFormatterInterface;
/**
* Base class for output classes.
*
* There are five levels of verbosity:
*
* * normal: no option passed (normal output)
* * verbose: -v (more output)
* * very verbose: -vv (highly extended output)
* * debug: -vvv (all debug output)
* * quiet: -q (no output)
*
* @author Fabien Potencier <fabien@symfony.com>
*/
abstract class Output implements OutputInterface
{
private $verbosity;
private $formatter;
/**
* @param int $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface)
* @param bool $decorated Whether to decorate messages
* @param OutputFormatterInterface|null $formatter Output formatter instance (null to use default OutputFormatter)
*/
public function __construct(?int $verbosity = self::VERBOSITY_NORMAL, bool $decorated = false, OutputFormatterInterface $formatter = null)
{
$this->verbosity = null === $verbosity ? self::VERBOSITY_NORMAL : $verbosity;
$this->formatter = $formatter ?: new OutputFormatter();
$this->formatter->setDecorated($decorated);
}
/**
* {@inheritdoc}
*/
public function setFormatter(OutputFormatterInterface $formatter)
{
$this->formatter = $formatter;
}
/**
* {@inheritdoc}
*/
public function getFormatter()
{
return $this->formatter;
}
/**
* {@inheritdoc}
*/
public function setDecorated(bool $decorated)
{
$this->formatter->setDecorated($decorated);
}
/**
* {@inheritdoc}
*/
public function isDecorated()
{
return $this->formatter->isDecorated();
}
/**
* {@inheritdoc}
*/
public function setVerbosity(int $level)
{
$this->verbosity = $level;
}
/**
* {@inheritdoc}
*/
public function getVerbosity()
{
return $this->verbosity;
}
/**
* {@inheritdoc}
*/
public function isQuiet()
{
return self::VERBOSITY_QUIET === $this->verbosity;
}
/**
* {@inheritdoc}
*/
public function isVerbose()
{
return self::VERBOSITY_VERBOSE <= $this->verbosity;
}
/**
* {@inheritdoc}
*/
public function isVeryVerbose()
{
return self::VERBOSITY_VERY_VERBOSE <= $this->verbosity;
}
/**
* {@inheritdoc}
*/
public function isDebug()
{
return self::VERBOSITY_DEBUG <= $this->verbosity;
}
/**
* {@inheritdoc}
*/
public function writeln($messages, int $options = self::OUTPUT_NORMAL)
{
$this->write($messages, true, $options);
}
/**
* {@inheritdoc}
*/
public function write($messages, bool $newline = false, int $options = self::OUTPUT_NORMAL)
{
if (!is_iterable($messages)) {
$messages = [$messages];
}
$types = self::OUTPUT_NORMAL | self::OUTPUT_RAW | self::OUTPUT_PLAIN;
$type = $types & $options ?: self::OUTPUT_NORMAL;
$verbosities = self::VERBOSITY_QUIET | self::VERBOSITY_NORMAL | self::VERBOSITY_VERBOSE | self::VERBOSITY_VERY_VERBOSE | self::VERBOSITY_DEBUG;
$verbosity = $verbosities & $options ?: self::VERBOSITY_NORMAL;
if ($verbosity > $this->getVerbosity()) {
return;
}
foreach ($messages as $message) {
switch ($type) {
case OutputInterface::OUTPUT_NORMAL:
$message = $this->formatter->format($message);
break;
case OutputInterface::OUTPUT_RAW:
break;
case OutputInterface::OUTPUT_PLAIN:
$message = strip_tags($this->formatter->format($message));
break;
}
$this->doWrite($message, $newline);
}
}
/**
* Writes a message to the output.
*/
abstract protected function doWrite(string $message, bool $newline);
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Output;
use Symfony\Component\Console\Formatter\OutputFormatterInterface;
/**
* OutputInterface is the interface implemented by all Output classes.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
interface OutputInterface
{
const VERBOSITY_QUIET = 16;
const VERBOSITY_NORMAL = 32;
const VERBOSITY_VERBOSE = 64;
const VERBOSITY_VERY_VERBOSE = 128;
const VERBOSITY_DEBUG = 256;
const OUTPUT_NORMAL = 1;
const OUTPUT_RAW = 2;
const OUTPUT_PLAIN = 4;
/**
* Writes a message to the output.
*
* @param string|iterable $messages The message as an iterable of strings or a single string
* @param bool $newline Whether to add a newline
* @param int $options A bitmask of options (one of the OUTPUT or VERBOSITY constants), 0 is considered the same as self::OUTPUT_NORMAL | self::VERBOSITY_NORMAL
*/
public function write($messages, bool $newline = false, int $options = 0);
/**
* Writes a message to the output and adds a newline at the end.
*
* @param string|iterable $messages The message as an iterable of strings or a single string
* @param int $options A bitmask of options (one of the OUTPUT or VERBOSITY constants), 0 is considered the same as self::OUTPUT_NORMAL | self::VERBOSITY_NORMAL
*/
public function writeln($messages, int $options = 0);
/**
* Sets the verbosity of the output.
*/
public function setVerbosity(int $level);
/**
* Gets the current verbosity of the output.
*
* @return int The current level of verbosity (one of the VERBOSITY constants)
*/
public function getVerbosity();
/**
* Returns whether verbosity is quiet (-q).
*
* @return bool true if verbosity is set to VERBOSITY_QUIET, false otherwise
*/
public function isQuiet();
/**
* Returns whether verbosity is verbose (-v).
*
* @return bool true if verbosity is set to VERBOSITY_VERBOSE, false otherwise
*/
public function isVerbose();
/**
* Returns whether verbosity is very verbose (-vv).
*
* @return bool true if verbosity is set to VERBOSITY_VERY_VERBOSE, false otherwise
*/
public function isVeryVerbose();
/**
* Returns whether verbosity is debug (-vvv).
*
* @return bool true if verbosity is set to VERBOSITY_DEBUG, false otherwise
*/
public function isDebug();
/**
* Sets the decorated flag.
*/
public function setDecorated(bool $decorated);
/**
* Gets the decorated flag.
*
* @return bool true if the output will decorate messages, false otherwise
*/
public function isDecorated();
public function setFormatter(OutputFormatterInterface $formatter);
/**
* Returns current output formatter instance.
*
* @return OutputFormatterInterface
*/
public function getFormatter();
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Descriptor;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Helper\Helper;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;
/**
* Text descriptor.
*
* @author Jean-François Simon <contact@jfsimon.fr>
*
* @internal
*/
class TextDescriptor extends Descriptor
{
/**
* {@inheritdoc}
*/
protected function describeInputArgument(InputArgument $argument, array $options = [])
{
if (null !== $argument->getDefault() && (!\is_array($argument->getDefault()) || \count($argument->getDefault()))) {
$default = sprintf('<comment> [default: %s]</comment>', $this->formatDefaultValue($argument->getDefault()));
} else {
$default = '';
}
$totalWidth = isset($options['total_width']) ? $options['total_width'] : Helper::strlen($argument->getName());
$spacingWidth = $totalWidth - \strlen($argument->getName());
$this->writeText(sprintf(' <info>%s</info> %s%s%s',
$argument->getName(),
str_repeat(' ', $spacingWidth),
// + 4 = 2 spaces before <info>, 2 spaces after </info>
preg_replace('/\s*[\r\n]\s*/', "\n".str_repeat(' ', $totalWidth + 4), $argument->getDescription()),
$default
), $options);
}
/**
* {@inheritdoc}
*/
protected function describeInputOption(InputOption $option, array $options = [])
{
if ($option->acceptValue() && null !== $option->getDefault() && (!\is_array($option->getDefault()) || \count($option->getDefault()))) {
$default = sprintf('<comment> [default: %s]</comment>', $this->formatDefaultValue($option->getDefault()));
} else {
$default = '';
}
$value = '';
if ($option->acceptValue()) {
$value = '='.strtoupper($option->getName());
if ($option->isValueOptional()) {
$value = '['.$value.']';
}
}
$totalWidth = isset($options['total_width']) ? $options['total_width'] : $this->calculateTotalWidthForOptions([$option]);
$synopsis = sprintf('%s%s',
$option->getShortcut() ? sprintf('-%s, ', $option->getShortcut()) : ' ',
sprintf('--%s%s', $option->getName(), $value)
);
$spacingWidth = $totalWidth - Helper::strlen($synopsis);
$this->writeText(sprintf(' <info>%s</info> %s%s%s%s',
$synopsis,
str_repeat(' ', $spacingWidth),
// + 4 = 2 spaces before <info>, 2 spaces after </info>
preg_replace('/\s*[\r\n]\s*/', "\n".str_repeat(' ', $totalWidth + 4), $option->getDescription()),
$default,
$option->isArray() ? '<comment> (multiple values allowed)</comment>' : ''
), $options);
}
/**
* {@inheritdoc}
*/
protected function describeInputDefinition(InputDefinition $definition, array $options = [])
{
$totalWidth = $this->calculateTotalWidthForOptions($definition->getOptions());
foreach ($definition->getArguments() as $argument) {
$totalWidth = max($totalWidth, Helper::strlen($argument->getName()));
}
if ($definition->getArguments()) {
$this->writeText('<comment>Arguments:</comment>', $options);
$this->writeText("\n");
foreach ($definition->getArguments() as $argument) {
$this->describeInputArgument($argument, array_merge($options, ['total_width' => $totalWidth]));
$this->writeText("\n");
}
}
if ($definition->getArguments() && $definition->getOptions()) {
$this->writeText("\n");
}
if ($definition->getOptions()) {
$laterOptions = [];
$this->writeText('<comment>Options:</comment>', $options);
foreach ($definition->getOptions() as $option) {
if (\strlen($option->getShortcut()) > 1) {
$laterOptions[] = $option;
continue;
}
$this->writeText("\n");
$this->describeInputOption($option, array_merge($options, ['total_width' => $totalWidth]));
}
foreach ($laterOptions as $option) {
$this->writeText("\n");
$this->describeInputOption($option, array_merge($options, ['total_width' => $totalWidth]));
}
}
}
/**
* {@inheritdoc}
*/
protected function describeCommand(Command $command, array $options = [])
{
$command->mergeApplicationDefinition(false);
if ($description = $command->getDescription()) {
$this->writeText('<comment>Description:</comment>', $options);
$this->writeText("\n");
$this->writeText(' '.$description);
$this->writeText("\n\n");
}
$this->writeText('<comment>Usage:</comment>', $options);
foreach (array_merge([$command->getSynopsis(true)], $command->getAliases(), $command->getUsages()) as $usage) {
$this->writeText("\n");
$this->writeText(' '.OutputFormatter::escape($usage), $options);
}
$this->writeText("\n");
$definition = $command->getDefinition();
if ($definition->getOptions() || $definition->getArguments()) {
$this->writeText("\n");
$this->describeInputDefinition($definition, $options);
$this->writeText("\n");
}
$help = $command->getProcessedHelp();
if ($help && $help !== $description) {
$this->writeText("\n");
$this->writeText('<comment>Help:</comment>', $options);
$this->writeText("\n");
$this->writeText(' '.str_replace("\n", "\n ", $help), $options);
$this->writeText("\n");
}
}
/**
* {@inheritdoc}
*/
protected function describeApplication(Application $application, array $options = [])
{
$describedNamespace = isset($options['namespace']) ? $options['namespace'] : null;
$description = new ApplicationDescription($application, $describedNamespace);
if (isset($options['raw_text']) && $options['raw_text']) {
$width = $this->getColumnWidth($description->getCommands());
foreach ($description->getCommands() as $command) {
$this->writeText(sprintf("%-{$width}s %s", $command->getName(), $command->getDescription()), $options);
$this->writeText("\n");
}
} else {
if ('' != $help = $application->getHelp()) {
$this->writeText("$help\n\n", $options);
}
$this->writeText("<comment>Usage:</comment>\n", $options);
$this->writeText(" command [options] [arguments]\n\n", $options);
$this->describeInputDefinition(new InputDefinition($application->getDefinition()->getOptions()), $options);
$this->writeText("\n");
$this->writeText("\n");
$commands = $description->getCommands();
$namespaces = $description->getNamespaces();
if ($describedNamespace && $namespaces) {
// make sure all alias commands are included when describing a specific namespace
$describedNamespaceInfo = reset($namespaces);
foreach ($describedNamespaceInfo['commands'] as $name) {
$commands[$name] = $description->getCommand($name);
}
}
// calculate max. width based on available commands per namespace
$width = $this->getColumnWidth(array_merge(...array_values(array_map(function ($namespace) use ($commands) {
return array_intersect($namespace['commands'], array_keys($commands));
}, array_values($namespaces)))));
if ($describedNamespace) {
$this->writeText(sprintf('<comment>Available commands for the "%s" namespace:</comment>', $describedNamespace), $options);
} else {
$this->writeText('<comment>Available commands:</comment>', $options);
}
foreach ($namespaces as $namespace) {
$namespace['commands'] = array_filter($namespace['commands'], function ($name) use ($commands) {
return isset($commands[$name]);
});
if (!$namespace['commands']) {
continue;
}
if (!$describedNamespace && ApplicationDescription::GLOBAL_NAMESPACE !== $namespace['id']) {
$this->writeText("\n");
$this->writeText(' <comment>'.$namespace['id'].'</comment>', $options);
}
foreach ($namespace['commands'] as $name) {
$this->writeText("\n");
$spacingWidth = $width - Helper::strlen($name);
$command = $commands[$name];
$commandAliases = $name === $command->getName() ? $this->getCommandAliasesText($command) : '';
$this->writeText(sprintf(' <info>%s</info>%s%s', $name, str_repeat(' ', $spacingWidth), $commandAliases.$command->getDescription()), $options);
}
}
$this->writeText("\n");
}
}
/**
* {@inheritdoc}
*/
private function writeText(string $content, array $options = [])
{
$this->write(
isset($options['raw_text']) && $options['raw_text'] ? strip_tags($content) : $content,
isset($options['raw_output']) ? !$options['raw_output'] : true
);
}
/**
* Formats command aliases to show them in the command description.
*/
private function getCommandAliasesText(Command $command): string
{
$text = '';
$aliases = $command->getAliases();
if ($aliases) {
$text = '['.implode('|', $aliases).'] ';
}
return $text;
}
/**
* Formats input option/argument default value.
*
* @param mixed $default
*/
private function formatDefaultValue($default): string
{
if (\INF === $default) {
return 'INF';
}
if (\is_string($default)) {
$default = OutputFormatter::escape($default);
} elseif (\is_array($default)) {
foreach ($default as $key => $value) {
if (\is_string($value)) {
$default[$key] = OutputFormatter::escape($value);
}
}
}
return str_replace('\\\\', '\\', json_encode($default, \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE));
}
/**
* @param (Command|string)[] $commands
*/
private function getColumnWidth(array $commands): int
{
$widths = [];
foreach ($commands as $command) {
if ($command instanceof Command) {
$widths[] = Helper::strlen($command->getName());
foreach ($command->getAliases() as $alias) {
$widths[] = Helper::strlen($alias);
}
} else {
$widths[] = Helper::strlen($command);
}
}
return $widths ? max($widths) + 2 : 0;
}
/**
* @param InputOption[] $options
*/
private function calculateTotalWidthForOptions(array $options): int
{
$totalWidth = 0;
foreach ($options as $option) {
// "-" + shortcut + ", --" + name
$nameLength = 1 + max(Helper::strlen($option->getShortcut()), 1) + 4 + Helper::strlen($option->getName());
if ($option->acceptValue()) {
$valueLength = 1 + Helper::strlen($option->getName()); // = + value
$valueLength += $option->isValueOptional() ? 2 : 0; // [ + ]
$nameLength += $valueLength;
}
$totalWidth = max($totalWidth, $nameLength);
}
return $totalWidth;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Descriptor;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\CommandNotFoundException;
/**
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
*
* @internal
*/
class ApplicationDescription
{
const GLOBAL_NAMESPACE = '_global';
private $application;
private $namespace;
private $showHidden;
/**
* @var array
*/
private $namespaces;
/**
* @var Command[]
*/
private $commands;
/**
* @var Command[]
*/
private $aliases;
public function __construct(Application $application, string $namespace = null, bool $showHidden = false)
{
$this->application = $application;
$this->namespace = $namespace;
$this->showHidden = $showHidden;
}
public function getNamespaces(): array
{
if (null === $this->namespaces) {
$this->inspectApplication();
}
return $this->namespaces;
}
/**
* @return Command[]
*/
public function getCommands(): array
{
if (null === $this->commands) {
$this->inspectApplication();
}
return $this->commands;
}
/**
* @throws CommandNotFoundException
*/
public function getCommand(string $name): Command
{
if (!isset($this->commands[$name]) && !isset($this->aliases[$name])) {
throw new CommandNotFoundException(sprintf('Command "%s" does not exist.', $name));
}
return isset($this->commands[$name]) ? $this->commands[$name] : $this->aliases[$name];
}
private function inspectApplication()
{
$this->commands = [];
$this->namespaces = [];
$all = $this->application->all($this->namespace ? $this->application->findNamespace($this->namespace) : null);
foreach ($this->sortCommands($all) as $namespace => $commands) {
$names = [];
/** @var Command $command */
foreach ($commands as $name => $command) {
if (!$command->getName() || (!$this->showHidden && $command->isHidden())) {
continue;
}
if ($command->getName() === $name) {
$this->commands[$name] = $command;
} else {
$this->aliases[$name] = $command;
}
$names[] = $name;
}
$this->namespaces[$namespace] = ['id' => $namespace, 'commands' => $names];
}
}
private function sortCommands(array $commands): array
{
$namespacedCommands = [];
$globalCommands = [];
$sortedCommands = [];
foreach ($commands as $name => $command) {
$key = $this->application->extractNamespace($name, 1);
if (\in_array($key, ['', self::GLOBAL_NAMESPACE], true)) {
$globalCommands[$name] = $command;
} else {
$namespacedCommands[$key][$name] = $command;
}
}
if ($globalCommands) {
ksort($globalCommands);
$sortedCommands[self::GLOBAL_NAMESPACE] = $globalCommands;
}
if ($namespacedCommands) {
ksort($namespacedCommands);
foreach ($namespacedCommands as $key => $commandsSet) {
ksort($commandsSet);
$sortedCommands[$key] = $commandsSet;
}
}
return $sortedCommands;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Descriptor;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Helper\Helper;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Markdown descriptor.
*
* @author Jean-François Simon <contact@jfsimon.fr>
*
* @internal
*/
class MarkdownDescriptor extends Descriptor
{
/**
* {@inheritdoc}
*/
public function describe(OutputInterface $output, $object, array $options = [])
{
$decorated = $output->isDecorated();
$output->setDecorated(false);
parent::describe($output, $object, $options);
$output->setDecorated($decorated);
}
/**
* {@inheritdoc}
*/
protected function write(string $content, bool $decorated = true)
{
parent::write($content, $decorated);
}
/**
* {@inheritdoc}
*/
protected function describeInputArgument(InputArgument $argument, array $options = [])
{
$this->write(
'#### `'.($argument->getName() ?: '<none>')."`\n\n"
.($argument->getDescription() ? preg_replace('/\s*[\r\n]\s*/', "\n", $argument->getDescription())."\n\n" : '')
.'* Is required: '.($argument->isRequired() ? 'yes' : 'no')."\n"
.'* Is array: '.($argument->isArray() ? 'yes' : 'no')."\n"
.'* Default: `'.str_replace("\n", '', var_export($argument->getDefault(), true)).'`'
);
}
/**
* {@inheritdoc}
*/
protected function describeInputOption(InputOption $option, array $options = [])
{
$name = '--'.$option->getName();
if ($option->getShortcut()) {
$name .= '|-'.str_replace('|', '|-', $option->getShortcut()).'';
}
$this->write(
'#### `'.$name.'`'."\n\n"
.($option->getDescription() ? preg_replace('/\s*[\r\n]\s*/', "\n", $option->getDescription())."\n\n" : '')
.'* Accept value: '.($option->acceptValue() ? 'yes' : 'no')."\n"
.'* Is value required: '.($option->isValueRequired() ? 'yes' : 'no')."\n"
.'* Is multiple: '.($option->isArray() ? 'yes' : 'no')."\n"
.'* Default: `'.str_replace("\n", '', var_export($option->getDefault(), true)).'`'
);
}
/**
* {@inheritdoc}
*/
protected function describeInputDefinition(InputDefinition $definition, array $options = [])
{
if ($showArguments = \count($definition->getArguments()) > 0) {
$this->write('### Arguments');
foreach ($definition->getArguments() as $argument) {
$this->write("\n\n");
if (null !== $describeInputArgument = $this->describeInputArgument($argument)) {
$this->write($describeInputArgument);
}
}
}
if (\count($definition->getOptions()) > 0) {
if ($showArguments) {
$this->write("\n\n");
}
$this->write('### Options');
foreach ($definition->getOptions() as $option) {
$this->write("\n\n");
if (null !== $describeInputOption = $this->describeInputOption($option)) {
$this->write($describeInputOption);
}
}
}
}
/**
* {@inheritdoc}
*/
protected function describeCommand(Command $command, array $options = [])
{
$command->mergeApplicationDefinition(false);
$this->write(
'`'.$command->getName()."`\n"
.str_repeat('-', Helper::strlen($command->getName()) + 2)."\n\n"
.($command->getDescription() ? $command->getDescription()."\n\n" : '')
.'### Usage'."\n\n"
.array_reduce(array_merge([$command->getSynopsis()], $command->getAliases(), $command->getUsages()), function ($carry, $usage) {
return $carry.'* `'.$usage.'`'."\n";
})
);
if ($help = $command->getProcessedHelp()) {
$this->write("\n");
$this->write($help);
}
$definition = $command->getDefinition();
if ($definition->getOptions() || $definition->getArguments()) {
$this->write("\n\n");
$this->describeInputDefinition($definition);
}
}
/**
* {@inheritdoc}
*/
protected function describeApplication(Application $application, array $options = [])
{
$describedNamespace = isset($options['namespace']) ? $options['namespace'] : null;
$description = new ApplicationDescription($application, $describedNamespace);
$title = $this->getApplicationTitle($application);
$this->write($title."\n".str_repeat('=', Helper::strlen($title)));
foreach ($description->getNamespaces() as $namespace) {
if (ApplicationDescription::GLOBAL_NAMESPACE !== $namespace['id']) {
$this->write("\n\n");
$this->write('**'.$namespace['id'].':**');
}
$this->write("\n\n");
$this->write(implode("\n", array_map(function ($commandName) use ($description) {
return sprintf('* [`%s`](#%s)', $commandName, str_replace(':', '', $description->getCommand($commandName)->getName()));
}, $namespace['commands'])));
}
foreach ($description->getCommands() as $command) {
$this->write("\n\n");
if (null !== $describeCommand = $this->describeCommand($command)) {
$this->write($describeCommand);
}
}
}
private function getApplicationTitle(Application $application): string
{
if ('UNKNOWN' !== $application->getName()) {
if ('UNKNOWN' !== $application->getVersion()) {
return sprintf('%s %s', $application->getName(), $application->getVersion());
}
return $application->getName();
}
return 'Console Tool';
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Descriptor;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Descriptor interface.
*
* @author Jean-François Simon <contact@jfsimon.fr>
*/
interface DescriptorInterface
{
/**
* Describes an object if supported.
*
* @param object $object
*/
public function describe(OutputInterface $output, $object, array $options = []);
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Descriptor;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;
/**
* XML descriptor.
*
* @author Jean-François Simon <contact@jfsimon.fr>
*
* @internal
*/
class XmlDescriptor extends Descriptor
{
public function getInputDefinitionDocument(InputDefinition $definition): \DOMDocument
{
$dom = new \DOMDocument('1.0', 'UTF-8');
$dom->appendChild($definitionXML = $dom->createElement('definition'));
$definitionXML->appendChild($argumentsXML = $dom->createElement('arguments'));
foreach ($definition->getArguments() as $argument) {
$this->appendDocument($argumentsXML, $this->getInputArgumentDocument($argument));
}
$definitionXML->appendChild($optionsXML = $dom->createElement('options'));
foreach ($definition->getOptions() as $option) {
$this->appendDocument($optionsXML, $this->getInputOptionDocument($option));
}
return $dom;
}
public function getCommandDocument(Command $command): \DOMDocument
{
$dom = new \DOMDocument('1.0', 'UTF-8');
$dom->appendChild($commandXML = $dom->createElement('command'));
$command->mergeApplicationDefinition(false);
$commandXML->setAttribute('id', $command->getName());
$commandXML->setAttribute('name', $command->getName());
$commandXML->setAttribute('hidden', $command->isHidden() ? 1 : 0);
$commandXML->appendChild($usagesXML = $dom->createElement('usages'));
foreach (array_merge([$command->getSynopsis()], $command->getAliases(), $command->getUsages()) as $usage) {
$usagesXML->appendChild($dom->createElement('usage', $usage));
}
$commandXML->appendChild($descriptionXML = $dom->createElement('description'));
$descriptionXML->appendChild($dom->createTextNode(str_replace("\n", "\n ", $command->getDescription())));
$commandXML->appendChild($helpXML = $dom->createElement('help'));
$helpXML->appendChild($dom->createTextNode(str_replace("\n", "\n ", $command->getProcessedHelp())));
$definitionXML = $this->getInputDefinitionDocument($command->getDefinition());
$this->appendDocument($commandXML, $definitionXML->getElementsByTagName('definition')->item(0));
return $dom;
}
public function getApplicationDocument(Application $application, string $namespace = null): \DOMDocument
{
$dom = new \DOMDocument('1.0', 'UTF-8');
$dom->appendChild($rootXml = $dom->createElement('symfony'));
if ('UNKNOWN' !== $application->getName()) {
$rootXml->setAttribute('name', $application->getName());
if ('UNKNOWN' !== $application->getVersion()) {
$rootXml->setAttribute('version', $application->getVersion());
}
}
$rootXml->appendChild($commandsXML = $dom->createElement('commands'));
$description = new ApplicationDescription($application, $namespace, true);
if ($namespace) {
$commandsXML->setAttribute('namespace', $namespace);
}
foreach ($description->getCommands() as $command) {
$this->appendDocument($commandsXML, $this->getCommandDocument($command));
}
if (!$namespace) {
$rootXml->appendChild($namespacesXML = $dom->createElement('namespaces'));
foreach ($description->getNamespaces() as $namespaceDescription) {
$namespacesXML->appendChild($namespaceArrayXML = $dom->createElement('namespace'));
$namespaceArrayXML->setAttribute('id', $namespaceDescription['id']);
foreach ($namespaceDescription['commands'] as $name) {
$namespaceArrayXML->appendChild($commandXML = $dom->createElement('command'));
$commandXML->appendChild($dom->createTextNode($name));
}
}
}
return $dom;
}
/**
* {@inheritdoc}
*/
protected function describeInputArgument(InputArgument $argument, array $options = [])
{
$this->writeDocument($this->getInputArgumentDocument($argument));
}
/**
* {@inheritdoc}
*/
protected function describeInputOption(InputOption $option, array $options = [])
{
$this->writeDocument($this->getInputOptionDocument($option));
}
/**
* {@inheritdoc}
*/
protected function describeInputDefinition(InputDefinition $definition, array $options = [])
{
$this->writeDocument($this->getInputDefinitionDocument($definition));
}
/**
* {@inheritdoc}
*/
protected function describeCommand(Command $command, array $options = [])
{
$this->writeDocument($this->getCommandDocument($command));
}
/**
* {@inheritdoc}
*/
protected function describeApplication(Application $application, array $options = [])
{
$this->writeDocument($this->getApplicationDocument($application, isset($options['namespace']) ? $options['namespace'] : null));
}
/**
* Appends document children to parent node.
*/
private function appendDocument(\DOMNode $parentNode, \DOMNode $importedParent)
{
foreach ($importedParent->childNodes as $childNode) {
$parentNode->appendChild($parentNode->ownerDocument->importNode($childNode, true));
}
}
/**
* Writes DOM document.
*/
private function writeDocument(\DOMDocument $dom)
{
$dom->formatOutput = true;
$this->write($dom->saveXML());
}
private function getInputArgumentDocument(InputArgument $argument): \DOMDocument
{
$dom = new \DOMDocument('1.0', 'UTF-8');
$dom->appendChild($objectXML = $dom->createElement('argument'));
$objectXML->setAttribute('name', $argument->getName());
$objectXML->setAttribute('is_required', $argument->isRequired() ? 1 : 0);
$objectXML->setAttribute('is_array', $argument->isArray() ? 1 : 0);
$objectXML->appendChild($descriptionXML = $dom->createElement('description'));
$descriptionXML->appendChild($dom->createTextNode($argument->getDescription()));
$objectXML->appendChild($defaultsXML = $dom->createElement('defaults'));
$defaults = \is_array($argument->getDefault()) ? $argument->getDefault() : (\is_bool($argument->getDefault()) ? [var_export($argument->getDefault(), true)] : ($argument->getDefault() ? [$argument->getDefault()] : []));
foreach ($defaults as $default) {
$defaultsXML->appendChild($defaultXML = $dom->createElement('default'));
$defaultXML->appendChild($dom->createTextNode($default));
}
return $dom;
}
private function getInputOptionDocument(InputOption $option): \DOMDocument
{
$dom = new \DOMDocument('1.0', 'UTF-8');
$dom->appendChild($objectXML = $dom->createElement('option'));
$objectXML->setAttribute('name', '--'.$option->getName());
$pos = strpos($option->getShortcut(), '|');
if (false !== $pos) {
$objectXML->setAttribute('shortcut', '-'.substr($option->getShortcut(), 0, $pos));
$objectXML->setAttribute('shortcuts', '-'.str_replace('|', '|-', $option->getShortcut()));
} else {
$objectXML->setAttribute('shortcut', $option->getShortcut() ? '-'.$option->getShortcut() : '');
}
$objectXML->setAttribute('accept_value', $option->acceptValue() ? 1 : 0);
$objectXML->setAttribute('is_value_required', $option->isValueRequired() ? 1 : 0);
$objectXML->setAttribute('is_multiple', $option->isArray() ? 1 : 0);
$objectXML->appendChild($descriptionXML = $dom->createElement('description'));
$descriptionXML->appendChild($dom->createTextNode($option->getDescription()));
if ($option->acceptValue()) {
$defaults = \is_array($option->getDefault()) ? $option->getDefault() : (\is_bool($option->getDefault()) ? [var_export($option->getDefault(), true)] : ($option->getDefault() ? [$option->getDefault()] : []));
$objectXML->appendChild($defaultsXML = $dom->createElement('defaults'));
if (!empty($defaults)) {
foreach ($defaults as $default) {
$defaultsXML->appendChild($defaultXML = $dom->createElement('default'));
$defaultXML->appendChild($dom->createTextNode($default));
}
}
}
return $dom;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Descriptor;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
*
* @internal
*/
abstract class Descriptor implements DescriptorInterface
{
/**
* @var OutputInterface
*/
protected $output;
/**
* {@inheritdoc}
*/
public function describe(OutputInterface $output, $object, array $options = [])
{
$this->output = $output;
switch (true) {
case $object instanceof InputArgument:
$this->describeInputArgument($object, $options);
break;
case $object instanceof InputOption:
$this->describeInputOption($object, $options);
break;
case $object instanceof InputDefinition:
$this->describeInputDefinition($object, $options);
break;
case $object instanceof Command:
$this->describeCommand($object, $options);
break;
case $object instanceof Application:
$this->describeApplication($object, $options);
break;
default:
throw new InvalidArgumentException(sprintf('Object of type "%s" is not describable.', get_debug_type($object)));
}
}
/**
* Writes content to output.
*/
protected function write(string $content, bool $decorated = false)
{
$this->output->write($content, false, $decorated ? OutputInterface::OUTPUT_NORMAL : OutputInterface::OUTPUT_RAW);
}
/**
* Describes an InputArgument instance.
*
* @return string|mixed
*/
abstract protected function describeInputArgument(InputArgument $argument, array $options = []);
/**
* Describes an InputOption instance.
*
* @return string|mixed
*/
abstract protected function describeInputOption(InputOption $option, array $options = []);
/**
* Describes an InputDefinition instance.
*
* @return string|mixed
*/
abstract protected function describeInputDefinition(InputDefinition $definition, array $options = []);
/**
* Describes a Command instance.
*
* @return string|mixed
*/
abstract protected function describeCommand(Command $command, array $options = []);
/**
* Describes an Application instance.
*
* @return string|mixed
*/
abstract protected function describeApplication(Application $application, array $options = []);
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Descriptor;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;
/**
* JSON descriptor.
*
* @author Jean-François Simon <contact@jfsimon.fr>
*
* @internal
*/
class JsonDescriptor extends Descriptor
{
/**
* {@inheritdoc}
*/
protected function describeInputArgument(InputArgument $argument, array $options = [])
{
$this->writeData($this->getInputArgumentData($argument), $options);
}
/**
* {@inheritdoc}
*/
protected function describeInputOption(InputOption $option, array $options = [])
{
$this->writeData($this->getInputOptionData($option), $options);
}
/**
* {@inheritdoc}
*/
protected function describeInputDefinition(InputDefinition $definition, array $options = [])
{
$this->writeData($this->getInputDefinitionData($definition), $options);
}
/**
* {@inheritdoc}
*/
protected function describeCommand(Command $command, array $options = [])
{
$this->writeData($this->getCommandData($command), $options);
}
/**
* {@inheritdoc}
*/
protected function describeApplication(Application $application, array $options = [])
{
$describedNamespace = isset($options['namespace']) ? $options['namespace'] : null;
$description = new ApplicationDescription($application, $describedNamespace, true);
$commands = [];
foreach ($description->getCommands() as $command) {
$commands[] = $this->getCommandData($command);
}
$data = [];
if ('UNKNOWN' !== $application->getName()) {
$data['application']['name'] = $application->getName();
if ('UNKNOWN' !== $application->getVersion()) {
$data['application']['version'] = $application->getVersion();
}
}
$data['commands'] = $commands;
if ($describedNamespace) {
$data['namespace'] = $describedNamespace;
} else {
$data['namespaces'] = array_values($description->getNamespaces());
}
$this->writeData($data, $options);
}
/**
* Writes data as json.
*/
private function writeData(array $data, array $options)
{
$flags = isset($options['json_encoding']) ? $options['json_encoding'] : 0;
$this->write(json_encode($data, $flags));
}
private function getInputArgumentData(InputArgument $argument): array
{
return [
'name' => $argument->getName(),
'is_required' => $argument->isRequired(),
'is_array' => $argument->isArray(),
'description' => preg_replace('/\s*[\r\n]\s*/', ' ', $argument->getDescription()),
'default' => \INF === $argument->getDefault() ? 'INF' : $argument->getDefault(),
];
}
private function getInputOptionData(InputOption $option): array
{
return [
'name' => '--'.$option->getName(),
'shortcut' => $option->getShortcut() ? '-'.str_replace('|', '|-', $option->getShortcut()) : '',
'accept_value' => $option->acceptValue(),
'is_value_required' => $option->isValueRequired(),
'is_multiple' => $option->isArray(),
'description' => preg_replace('/\s*[\r\n]\s*/', ' ', $option->getDescription()),
'default' => \INF === $option->getDefault() ? 'INF' : $option->getDefault(),
];
}
private function getInputDefinitionData(InputDefinition $definition): array
{
$inputArguments = [];
foreach ($definition->getArguments() as $name => $argument) {
$inputArguments[$name] = $this->getInputArgumentData($argument);
}
$inputOptions = [];
foreach ($definition->getOptions() as $name => $option) {
$inputOptions[$name] = $this->getInputOptionData($option);
}
return ['arguments' => $inputArguments, 'options' => $inputOptions];
}
private function getCommandData(Command $command): array
{
$command->mergeApplicationDefinition(false);
return [
'name' => $command->getName(),
'usage' => array_merge([$command->getSynopsis()], $command->getUsages(), $command->getAliases()),
'description' => $command->getDescription(),
'help' => $command->getProcessedHelp(),
'definition' => $this->getInputDefinitionData($command->getDefinition()),
'hidden' => $command->isHidden(),
];
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\CommandLoader;
use Psr\Container\ContainerInterface;
use Symfony\Component\Console\Exception\CommandNotFoundException;
/**
* Loads commands from a PSR-11 container.
*
* @author Robin Chalas <robin.chalas@gmail.com>
*/
class ContainerCommandLoader implements CommandLoaderInterface
{
private $container;
private $commandMap;
/**
* @param array $commandMap An array with command names as keys and service ids as values
*/
public function __construct(ContainerInterface $container, array $commandMap)
{
$this->container = $container;
$this->commandMap = $commandMap;
}
/**
* {@inheritdoc}
*/
public function get(string $name)
{
if (!$this->has($name)) {
throw new CommandNotFoundException(sprintf('Command "%s" does not exist.', $name));
}
return $this->container->get($this->commandMap[$name]);
}
/**
* {@inheritdoc}
*/
public function has(string $name)
{
return isset($this->commandMap[$name]) && $this->container->has($this->commandMap[$name]);
}
/**
* {@inheritdoc}
*/
public function getNames()
{
return array_keys($this->commandMap);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\CommandLoader;
use Symfony\Component\Console\Exception\CommandNotFoundException;
/**
* A simple command loader using factories to instantiate commands lazily.
*
* @author Maxime Steinhausser <maxime.steinhausser@gmail.com>
*/
class FactoryCommandLoader implements CommandLoaderInterface
{
private $factories;
/**
* @param callable[] $factories Indexed by command names
*/
public function __construct(array $factories)
{
$this->factories = $factories;
}
/**
* {@inheritdoc}
*/
public function has(string $name)
{
return isset($this->factories[$name]);
}
/**
* {@inheritdoc}
*/
public function get(string $name)
{
if (!isset($this->factories[$name])) {
throw new CommandNotFoundException(sprintf('Command "%s" does not exist.', $name));
}
$factory = $this->factories[$name];
return $factory();
}
/**
* {@inheritdoc}
*/
public function getNames()
{
return array_keys($this->factories);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\CommandLoader;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\CommandNotFoundException;
/**
* @author Robin Chalas <robin.chalas@gmail.com>
*/
interface CommandLoaderInterface
{
/**
* Loads a command.
*
* @return Command
*
* @throws CommandNotFoundException
*/
public function get(string $name);
/**
* Checks if a command exists.
*
* @return bool
*/
public function has(string $name);
/**
* @return string[] All registered command names
*/
public function getNames();
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Exception;
/**
* ExceptionInterface.
*
* @author Jérôme Tamarelle <jerome@tamarelle.net>
*/
interface ExceptionInterface extends \Throwable
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Exception;
/**
* @author Jérôme Tamarelle <jerome@tamarelle.net>
*/
class RuntimeException extends \RuntimeException implements ExceptionInterface
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Exception;
/**
* Represents an incorrect namespace typed in the console.
*
* @author Pierre du Plessis <pdples@gmail.com>
*/
class NamespaceNotFoundException extends CommandNotFoundException
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Exception;
/**
* Represents failure to read input from stdin.
*
* @author Gabriel Ostrolucký <gabriel.ostrolucky@gmail.com>
*/
class MissingInputException extends RuntimeException implements ExceptionInterface
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Exception;
/**
* Represents an incorrect option name typed in the console.
*
* @author Jérôme Tamarelle <jerome@tamarelle.net>
*/
class InvalidOptionException extends \InvalidArgumentException implements ExceptionInterface
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Exception;
/**
* @author Jérôme Tamarelle <jerome@tamarelle.net>
*/
class LogicException extends \LogicException implements ExceptionInterface
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Exception;
/**
* Represents an incorrect command name typed in the console.
*
* @author Jérôme Tamarelle <jerome@tamarelle.net>
*/
class CommandNotFoundException extends \InvalidArgumentException implements ExceptionInterface
{
private $alternatives;
/**
* @param string $message Exception message to throw
* @param array $alternatives List of similar defined names
* @param int $code Exception code
* @param \Throwable $previous Previous exception used for the exception chaining
*/
public function __construct(string $message, array $alternatives = [], int $code = 0, \Throwable $previous = null)
{
parent::__construct($message, $code, $previous);
$this->alternatives = $alternatives;
}
/**
* @return array A list of similar defined names
*/
public function getAlternatives()
{
return $this->alternatives;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Exception;
/**
* @author Jérôme Tamarelle <jerome@tamarelle.net>
*/
class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Helper;
use Symfony\Component\Console\Formatter\OutputFormatter;
/**
* The Formatter class provides helpers to format messages.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class FormatterHelper extends Helper
{
/**
* Formats a message within a section.
*
* @return string The format section
*/
public function formatSection(string $section, string $message, string $style = 'info')
{
return sprintf('<%s>[%s]</%s> %s', $style, $section, $style, $message);
}
/**
* Formats a message as a block of text.
*
* @param string|array $messages The message to write in the block
*
* @return string The formatter message
*/
public function formatBlock($messages, string $style, bool $large = false)
{
if (!\is_array($messages)) {
$messages = [$messages];
}
$len = 0;
$lines = [];
foreach ($messages as $message) {
$message = OutputFormatter::escape($message);
$lines[] = sprintf($large ? ' %s ' : ' %s ', $message);
$len = max(self::strlen($message) + ($large ? 4 : 2), $len);
}
$messages = $large ? [str_repeat(' ', $len)] : [];
for ($i = 0; isset($lines[$i]); ++$i) {
$messages[] = $lines[$i].str_repeat(' ', $len - self::strlen($lines[$i]));
}
if ($large) {
$messages[] = str_repeat(' ', $len);
}
for ($i = 0; isset($messages[$i]); ++$i) {
$messages[$i] = sprintf('<%s>%s</%s>', $style, $messages[$i], $style);
}
return implode("\n", $messages);
}
/**
* Truncates a message to the given length.
*
* @return string
*/
public function truncate(string $message, int $length, string $suffix = '...')
{
$computedLength = $length - self::strlen($suffix);
if ($computedLength > self::strlen($message)) {
return $message;
}
return self::substr($message, 0, $length).$suffix;
}
/**
* {@inheritdoc}
*/
public function getName()
{
return 'formatter';
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Helper;
/**
* @internal
*/
class TableRows implements \IteratorAggregate
{
private $generator;
public function __construct(callable $generator)
{
$this->generator = $generator;
}
public function getIterator(): \Traversable
{
$g = $this->generator;
return $g();
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Helper;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\LogicException;
/**
* Defines the styles for a Table.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Саша Стаменковић <umpirsky@gmail.com>
* @author Dany Maillard <danymaillard93b@gmail.com>
*/
class TableStyle
{
private $paddingChar = ' ';
private $horizontalOutsideBorderChar = '-';
private $horizontalInsideBorderChar = '-';
private $verticalOutsideBorderChar = '|';
private $verticalInsideBorderChar = '|';
private $crossingChar = '+';
private $crossingTopRightChar = '+';
private $crossingTopMidChar = '+';
private $crossingTopLeftChar = '+';
private $crossingMidRightChar = '+';
private $crossingBottomRightChar = '+';
private $crossingBottomMidChar = '+';
private $crossingBottomLeftChar = '+';
private $crossingMidLeftChar = '+';
private $crossingTopLeftBottomChar = '+';
private $crossingTopMidBottomChar = '+';
private $crossingTopRightBottomChar = '+';
private $headerTitleFormat = '<fg=black;bg=white;options=bold> %s </>';
private $footerTitleFormat = '<fg=black;bg=white;options=bold> %s </>';
private $cellHeaderFormat = '<info>%s</info>';
private $cellRowFormat = '%s';
private $cellRowContentFormat = ' %s ';
private $borderFormat = '%s';
private $padType = \STR_PAD_RIGHT;
/**
* Sets padding character, used for cell padding.
*
* @return $this
*/
public function setPaddingChar(string $paddingChar)
{
if (!$paddingChar) {
throw new LogicException('The padding char must not be empty.');
}
$this->paddingChar = $paddingChar;
return $this;
}
/**
* Gets padding character, used for cell padding.
*
* @return string
*/
public function getPaddingChar()
{
return $this->paddingChar;
}
/**
* Sets horizontal border characters.
*
* <code>
* ╔═══════════════╤══════════════════════════╤══════════════════╗
* 1 ISBN 2 Title │ Author ║
* ╠═══════════════╪══════════════════════════╪══════════════════╣
* ║ 99921-58-10-7 │ Divine Comedy │ Dante Alighieri ║
* ║ 9971-5-0210-0 │ A Tale of Two Cities │ Charles Dickens ║
* ║ 960-425-059-0 │ The Lord of the Rings │ J. R. R. Tolkien ║
* ║ 80-902734-1-6 │ And Then There Were None │ Agatha Christie ║
* ╚═══════════════╧══════════════════════════╧══════════════════╝
* </code>
*/
public function setHorizontalBorderChars(string $outside, string $inside = null): self
{
$this->horizontalOutsideBorderChar = $outside;
$this->horizontalInsideBorderChar = $inside ?? $outside;
return $this;
}
/**
* Sets vertical border characters.
*
* <code>
* ╔═══════════════╤══════════════════════════╤══════════════════╗
* ║ ISBN │ Title │ Author ║
* ╠═══════1═══════╪══════════════════════════╪══════════════════╣
* ║ 99921-58-10-7 │ Divine Comedy │ Dante Alighieri ║
* ║ 9971-5-0210-0 │ A Tale of Two Cities │ Charles Dickens ║
* ╟───────2───────┼──────────────────────────┼──────────────────╢
* ║ 960-425-059-0 │ The Lord of the Rings │ J. R. R. Tolkien ║
* ║ 80-902734-1-6 │ And Then There Were None │ Agatha Christie ║
* ╚═══════════════╧══════════════════════════╧══════════════════╝
* </code>
*/
public function setVerticalBorderChars(string $outside, string $inside = null): self
{
$this->verticalOutsideBorderChar = $outside;
$this->verticalInsideBorderChar = $inside ?? $outside;
return $this;
}
/**
* Gets border characters.
*
* @internal
*/
public function getBorderChars(): array
{
return [
$this->horizontalOutsideBorderChar,
$this->verticalOutsideBorderChar,
$this->horizontalInsideBorderChar,
$this->verticalInsideBorderChar,
];
}
/**
* Sets crossing characters.
*
* Example:
* <code>
* 1═══════════════2══════════════════════════2══════════════════3
* ║ ISBN │ Title │ Author ║
* 8'══════════════0'═════════════════════════0'═════════════════4'
* ║ 99921-58-10-7 │ Divine Comedy │ Dante Alighieri ║
* ║ 9971-5-0210-0 │ A Tale of Two Cities │ Charles Dickens ║
* 8───────────────0──────────────────────────0──────────────────4
* ║ 960-425-059-0 │ The Lord of the Rings │ J. R. R. Tolkien ║
* ║ 80-902734-1-6 │ And Then There Were None │ Agatha Christie ║
* 7═══════════════6══════════════════════════6══════════════════5
* </code>
*
* @param string $cross Crossing char (see #0 of example)
* @param string $topLeft Top left char (see #1 of example)
* @param string $topMid Top mid char (see #2 of example)
* @param string $topRight Top right char (see #3 of example)
* @param string $midRight Mid right char (see #4 of example)
* @param string $bottomRight Bottom right char (see #5 of example)
* @param string $bottomMid Bottom mid char (see #6 of example)
* @param string $bottomLeft Bottom left char (see #7 of example)
* @param string $midLeft Mid left char (see #8 of example)
* @param string|null $topLeftBottom Top left bottom char (see #8' of example), equals to $midLeft if null
* @param string|null $topMidBottom Top mid bottom char (see #0' of example), equals to $cross if null
* @param string|null $topRightBottom Top right bottom char (see #4' of example), equals to $midRight if null
*/
public function setCrossingChars(string $cross, string $topLeft, string $topMid, string $topRight, string $midRight, string $bottomRight, string $bottomMid, string $bottomLeft, string $midLeft, string $topLeftBottom = null, string $topMidBottom = null, string $topRightBottom = null): self
{
$this->crossingChar = $cross;
$this->crossingTopLeftChar = $topLeft;
$this->crossingTopMidChar = $topMid;
$this->crossingTopRightChar = $topRight;
$this->crossingMidRightChar = $midRight;
$this->crossingBottomRightChar = $bottomRight;
$this->crossingBottomMidChar = $bottomMid;
$this->crossingBottomLeftChar = $bottomLeft;
$this->crossingMidLeftChar = $midLeft;
$this->crossingTopLeftBottomChar = $topLeftBottom ?? $midLeft;
$this->crossingTopMidBottomChar = $topMidBottom ?? $cross;
$this->crossingTopRightBottomChar = $topRightBottom ?? $midRight;
return $this;
}
/**
* Sets default crossing character used for each cross.
*
* @see {@link setCrossingChars()} for setting each crossing individually.
*/
public function setDefaultCrossingChar(string $char): self
{
return $this->setCrossingChars($char, $char, $char, $char, $char, $char, $char, $char, $char);
}
/**
* Gets crossing character.
*
* @return string
*/
public function getCrossingChar()
{
return $this->crossingChar;
}
/**
* Gets crossing characters.
*
* @internal
*/
public function getCrossingChars(): array
{
return [
$this->crossingChar,
$this->crossingTopLeftChar,
$this->crossingTopMidChar,
$this->crossingTopRightChar,
$this->crossingMidRightChar,
$this->crossingBottomRightChar,
$this->crossingBottomMidChar,
$this->crossingBottomLeftChar,
$this->crossingMidLeftChar,
$this->crossingTopLeftBottomChar,
$this->crossingTopMidBottomChar,
$this->crossingTopRightBottomChar,
];
}
/**
* Sets header cell format.
*
* @return $this
*/
public function setCellHeaderFormat(string $cellHeaderFormat)
{
$this->cellHeaderFormat = $cellHeaderFormat;
return $this;
}
/**
* Gets header cell format.
*
* @return string
*/
public function getCellHeaderFormat()
{
return $this->cellHeaderFormat;
}
/**
* Sets row cell format.
*
* @return $this
*/
public function setCellRowFormat(string $cellRowFormat)
{
$this->cellRowFormat = $cellRowFormat;
return $this;
}
/**
* Gets row cell format.
*
* @return string
*/
public function getCellRowFormat()
{
return $this->cellRowFormat;
}
/**
* Sets row cell content format.
*
* @return $this
*/
public function setCellRowContentFormat(string $cellRowContentFormat)
{
$this->cellRowContentFormat = $cellRowContentFormat;
return $this;
}
/**
* Gets row cell content format.
*
* @return string
*/
public function getCellRowContentFormat()
{
return $this->cellRowContentFormat;
}
/**
* Sets table border format.
*
* @return $this
*/
public function setBorderFormat(string $borderFormat)
{
$this->borderFormat = $borderFormat;
return $this;
}
/**
* Gets table border format.
*
* @return string
*/
public function getBorderFormat()
{
return $this->borderFormat;
}
/**
* Sets cell padding type.
*
* @return $this
*/
public function setPadType(int $padType)
{
if (!\in_array($padType, [\STR_PAD_LEFT, \STR_PAD_RIGHT, \STR_PAD_BOTH], true)) {
throw new InvalidArgumentException('Invalid padding type. Expected one of (STR_PAD_LEFT, STR_PAD_RIGHT, STR_PAD_BOTH).');
}
$this->padType = $padType;
return $this;
}
/**
* Gets cell padding type.
*
* @return int
*/
public function getPadType()
{
return $this->padType;
}
public function getHeaderTitleFormat(): string
{
return $this->headerTitleFormat;
}
public function setHeaderTitleFormat(string $format): self
{
$this->headerTitleFormat = $format;
return $this;
}
public function getFooterTitleFormat(): string
{
return $this->footerTitleFormat;
}
public function setFooterTitleFormat(string $format): self
{
$this->footerTitleFormat = $format;
return $this;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Helper;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ChoiceQuestion;
use Symfony\Component\Console\Question\ConfirmationQuestion;
use Symfony\Component\Console\Question\Question;
use Symfony\Component\Console\Style\SymfonyStyle;
/**
* Symfony Style Guide compliant question helper.
*
* @author Kevin Bond <kevinbond@gmail.com>
*/
class SymfonyQuestionHelper extends QuestionHelper
{
/**
* {@inheritdoc}
*/
protected function writePrompt(OutputInterface $output, Question $question)
{
$text = OutputFormatter::escapeTrailingBackslash($question->getQuestion());
$default = $question->getDefault();
if ($question->isMultiline()) {
$text .= sprintf(' (press %s to continue)', $this->getEofShortcut());
}
switch (true) {
case null === $default:
$text = sprintf(' <info>%s</info>:', $text);
break;
case $question instanceof ConfirmationQuestion:
$text = sprintf(' <info>%s (yes/no)</info> [<comment>%s</comment>]:', $text, $default ? 'yes' : 'no');
break;
case $question instanceof ChoiceQuestion && $question->isMultiselect():
$choices = $question->getChoices();
$default = explode(',', $default);
foreach ($default as $key => $value) {
$default[$key] = $choices[trim($value)];
}
$text = sprintf(' <info>%s</info> [<comment>%s</comment>]:', $text, OutputFormatter::escape(implode(', ', $default)));
break;
case $question instanceof ChoiceQuestion:
$choices = $question->getChoices();
$text = sprintf(' <info>%s</info> [<comment>%s</comment>]:', $text, OutputFormatter::escape(isset($choices[$default]) ? $choices[$default] : $default));
break;
default:
$text = sprintf(' <info>%s</info> [<comment>%s</comment>]:', $text, OutputFormatter::escape($default));
}
$output->writeln($text);
$prompt = ' > ';
if ($question instanceof ChoiceQuestion) {
$output->writeln($this->formatChoiceQuestionChoices($question, 'comment'));
$prompt = $question->getPrompt();
}
$output->write($prompt);
}
/**
* {@inheritdoc}
*/
protected function writeError(OutputInterface $output, \Exception $error)
{
if ($output instanceof SymfonyStyle) {
$output->newLine();
$output->error($error->getMessage());
return;
}
parent::writeError($output, $error);
}
private function getEofShortcut(): string
{
if (false !== strpos(\PHP_OS, 'WIN')) {
return '<comment>Ctrl+Z</comment> then <comment>Enter</comment>';
}
return '<comment>Ctrl+D</comment>';
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Helper;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\VarDumper\Cloner\ClonerInterface;
use Symfony\Component\VarDumper\Cloner\VarCloner;
use Symfony\Component\VarDumper\Dumper\CliDumper;
/**
* @author Roland Franssen <franssen.roland@gmail.com>
*/
final class Dumper
{
private $output;
private $dumper;
private $cloner;
private $handler;
public function __construct(OutputInterface $output, CliDumper $dumper = null, ClonerInterface $cloner = null)
{
$this->output = $output;
$this->dumper = $dumper;
$this->cloner = $cloner;
if (class_exists(CliDumper::class)) {
$this->handler = function ($var): string {
$dumper = $this->dumper ?? $this->dumper = new CliDumper(null, null, CliDumper::DUMP_LIGHT_ARRAY | CliDumper::DUMP_COMMA_SEPARATOR);
$dumper->setColors($this->output->isDecorated());
return rtrim($dumper->dump(($this->cloner ?? $this->cloner = new VarCloner())->cloneVar($var)->withRefHandles(false), true));
};
} else {
$this->handler = function ($var): string {
switch (true) {
case null === $var:
return 'null';
case true === $var:
return 'true';
case false === $var:
return 'false';
case \is_string($var):
return '"'.$var.'"';
default:
return rtrim(print_r($var, true));
}
};
}
}
public function __invoke($var): string
{
return ($this->handler)($var);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Helper;
use Symfony\Component\Console\Input\InputAwareInterface;
use Symfony\Component\Console\Input\InputInterface;
/**
* An implementation of InputAwareInterface for Helpers.
*
* @author Wouter J <waldio.webdesign@gmail.com>
*/
abstract class InputAwareHelper extends Helper implements InputAwareInterface
{
protected $input;
/**
* {@inheritdoc}
*/
public function setInput(InputInterface $input)
{
$this->input = $input;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Helper;
use Symfony\Component\Console\Descriptor\DescriptorInterface;
use Symfony\Component\Console\Descriptor\JsonDescriptor;
use Symfony\Component\Console\Descriptor\MarkdownDescriptor;
use Symfony\Component\Console\Descriptor\TextDescriptor;
use Symfony\Component\Console\Descriptor\XmlDescriptor;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Output\OutputInterface;
/**
* This class adds helper method to describe objects in various formats.
*
* @author Jean-François Simon <contact@jfsimon.fr>
*/
class DescriptorHelper extends Helper
{
/**
* @var DescriptorInterface[]
*/
private $descriptors = [];
public function __construct()
{
$this
->register('txt', new TextDescriptor())
->register('xml', new XmlDescriptor())
->register('json', new JsonDescriptor())
->register('md', new MarkdownDescriptor())
;
}
/**
* Describes an object if supported.
*
* Available options are:
* * format: string, the output format name
* * raw_text: boolean, sets output type as raw
*
* @throws InvalidArgumentException when the given format is not supported
*/
public function describe(OutputInterface $output, ?object $object, array $options = [])
{
$options = array_merge([
'raw_text' => false,
'format' => 'txt',
], $options);
if (!isset($this->descriptors[$options['format']])) {
throw new InvalidArgumentException(sprintf('Unsupported format "%s".', $options['format']));
}
$descriptor = $this->descriptors[$options['format']];
$descriptor->describe($output, $object, $options);
}
/**
* Registers a descriptor.
*
* @return $this
*/
public function register(string $format, DescriptorInterface $descriptor)
{
$this->descriptors[$format] = $descriptor;
return $this;
}
/**
* {@inheritdoc}
*/
public function getName()
{
return 'descriptor';
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Helper;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Process\Exception\ProcessFailedException;
use Symfony\Component\Process\Process;
/**
* The ProcessHelper class provides helpers to run external processes.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @final
*/
class ProcessHelper extends Helper
{
/**
* Runs an external process.
*
* @param array|Process $cmd An instance of Process or an array of the command and arguments
* @param callable|null $callback A PHP callback to run whenever there is some
* output available on STDOUT or STDERR
*
* @return Process The process that ran
*/
public function run(OutputInterface $output, $cmd, string $error = null, callable $callback = null, int $verbosity = OutputInterface::VERBOSITY_VERY_VERBOSE): Process
{
if (!class_exists(Process::class)) {
throw new \LogicException('The ProcessHelper cannot be run as the Process component is not installed. Try running "compose require symfony/process".');
}
if ($output instanceof ConsoleOutputInterface) {
$output = $output->getErrorOutput();
}
$formatter = $this->getHelperSet()->get('debug_formatter');
if ($cmd instanceof Process) {
$cmd = [$cmd];
}
if (!\is_array($cmd)) {
throw new \TypeError(sprintf('The "command" argument of "%s()" must be an array or a "%s" instance, "%s" given.', __METHOD__, Process::class, get_debug_type($cmd)));
}
if (\is_string($cmd[0] ?? null)) {
$process = new Process($cmd);
$cmd = [];
} elseif (($cmd[0] ?? null) instanceof Process) {
$process = $cmd[0];
unset($cmd[0]);
} else {
throw new \InvalidArgumentException(sprintf('Invalid command provided to "%s()": the command should be an array whose first element is either the path to the binary to run or a "Process" object.', __METHOD__));
}
if ($verbosity <= $output->getVerbosity()) {
$output->write($formatter->start(spl_object_hash($process), $this->escapeString($process->getCommandLine())));
}
if ($output->isDebug()) {
$callback = $this->wrapCallback($output, $process, $callback);
}
$process->run($callback, $cmd);
if ($verbosity <= $output->getVerbosity()) {
$message = $process->isSuccessful() ? 'Command ran successfully' : sprintf('%s Command did not run successfully', $process->getExitCode());
$output->write($formatter->stop(spl_object_hash($process), $message, $process->isSuccessful()));
}
if (!$process->isSuccessful() && null !== $error) {
$output->writeln(sprintf('<error>%s</error>', $this->escapeString($error)));
}
return $process;
}
/**
* Runs the process.
*
* This is identical to run() except that an exception is thrown if the process
* exits with a non-zero exit code.
*
* @param string|Process $cmd An instance of Process or a command to run
* @param callable|null $callback A PHP callback to run whenever there is some
* output available on STDOUT or STDERR
*
* @return Process The process that ran
*
* @throws ProcessFailedException
*
* @see run()
*/
public function mustRun(OutputInterface $output, $cmd, string $error = null, callable $callback = null): Process
{
$process = $this->run($output, $cmd, $error, $callback);
if (!$process->isSuccessful()) {
throw new ProcessFailedException($process);
}
return $process;
}
/**
* Wraps a Process callback to add debugging output.
*/
public function wrapCallback(OutputInterface $output, Process $process, callable $callback = null): callable
{
if ($output instanceof ConsoleOutputInterface) {
$output = $output->getErrorOutput();
}
$formatter = $this->getHelperSet()->get('debug_formatter');
return function ($type, $buffer) use ($output, $process, $callback, $formatter) {
$output->write($formatter->progress(spl_object_hash($process), $this->escapeString($buffer), Process::ERR === $type));
if (null !== $callback) {
$callback($type, $buffer);
}
};
}
private function escapeString(string $str): string
{
return str_replace('<', '\\<', $str);
}
/**
* {@inheritdoc}
*/
public function getName(): string
{
return 'process';
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Helper;
/**
* HelperInterface is the interface all helpers must implement.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
interface HelperInterface
{
/**
* Sets the helper set associated with this helper.
*/
public function setHelperSet(HelperSet $helperSet = null);
/**
* Gets the helper set associated with this helper.
*
* @return HelperSet A HelperSet instance
*/
public function getHelperSet();
/**
* Returns the canonical name of this helper.
*
* @return string The canonical name
*/
public function getName();
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Helper;
/**
* Marks a row as being a separator.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class TableSeparator extends TableCell
{
public function __construct(array $options = [])
{
parent::__construct('', $options);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Helper;
/**
* Helps outputting debug information when running an external program from a command.
*
* An external program can be a Process, an HTTP request, or anything else.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class DebugFormatterHelper extends Helper
{
private $colors = ['black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white', 'default'];
private $started = [];
private $count = -1;
/**
* Starts a debug formatting session.
*
* @return string
*/
public function start(string $id, string $message, string $prefix = 'RUN')
{
$this->started[$id] = ['border' => ++$this->count % \count($this->colors)];
return sprintf("%s<bg=blue;fg=white> %s </> <fg=blue>%s</>\n", $this->getBorder($id), $prefix, $message);
}
/**
* Adds progress to a formatting session.
*
* @return string
*/
public function progress(string $id, string $buffer, bool $error = false, string $prefix = 'OUT', string $errorPrefix = 'ERR')
{
$message = '';
if ($error) {
if (isset($this->started[$id]['out'])) {
$message .= "\n";
unset($this->started[$id]['out']);
}
if (!isset($this->started[$id]['err'])) {
$message .= sprintf('%s<bg=red;fg=white> %s </> ', $this->getBorder($id), $errorPrefix);
$this->started[$id]['err'] = true;
}
$message .= str_replace("\n", sprintf("\n%s<bg=red;fg=white> %s </> ", $this->getBorder($id), $errorPrefix), $buffer);
} else {
if (isset($this->started[$id]['err'])) {
$message .= "\n";
unset($this->started[$id]['err']);
}
if (!isset($this->started[$id]['out'])) {
$message .= sprintf('%s<bg=green;fg=white> %s </> ', $this->getBorder($id), $prefix);
$this->started[$id]['out'] = true;
}
$message .= str_replace("\n", sprintf("\n%s<bg=green;fg=white> %s </> ", $this->getBorder($id), $prefix), $buffer);
}
return $message;
}
/**
* Stops a formatting session.
*
* @return string
*/
public function stop(string $id, string $message, bool $successful, string $prefix = 'RES')
{
$trailingEOL = isset($this->started[$id]['out']) || isset($this->started[$id]['err']) ? "\n" : '';
if ($successful) {
return sprintf("%s%s<bg=green;fg=white> %s </> <fg=green>%s</>\n", $trailingEOL, $this->getBorder($id), $prefix, $message);
}
$message = sprintf("%s%s<bg=red;fg=white> %s </> <fg=red>%s</>\n", $trailingEOL, $this->getBorder($id), $prefix, $message);
unset($this->started[$id]['out'], $this->started[$id]['err']);
return $message;
}
private function getBorder(string $id): string
{
return sprintf('<bg=%s> </>', $this->colors[$this->started[$id]['border']]);
}
/**
* {@inheritdoc}
*/
public function getName()
{
return 'debug_formatter';
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Helper;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\Console\Output\OutputInterface;
/**
* @author Kevin Bond <kevinbond@gmail.com>
*/
class ProgressIndicator
{
private $output;
private $startTime;
private $format;
private $message;
private $indicatorValues;
private $indicatorCurrent;
private $indicatorChangeInterval;
private $indicatorUpdateTime;
private $started = false;
private static $formatters;
private static $formats;
/**
* @param int $indicatorChangeInterval Change interval in milliseconds
* @param array|null $indicatorValues Animated indicator characters
*/
public function __construct(OutputInterface $output, string $format = null, int $indicatorChangeInterval = 100, array $indicatorValues = null)
{
$this->output = $output;
if (null === $format) {
$format = $this->determineBestFormat();
}
if (null === $indicatorValues) {
$indicatorValues = ['-', '\\', '|', '/'];
}
$indicatorValues = array_values($indicatorValues);
if (2 > \count($indicatorValues)) {
throw new InvalidArgumentException('Must have at least 2 indicator value characters.');
}
$this->format = self::getFormatDefinition($format);
$this->indicatorChangeInterval = $indicatorChangeInterval;
$this->indicatorValues = $indicatorValues;
$this->startTime = time();
}
/**
* Sets the current indicator message.
*/
public function setMessage(?string $message)
{
$this->message = $message;
$this->display();
}
/**
* Starts the indicator output.
*/
public function start(string $message)
{
if ($this->started) {
throw new LogicException('Progress indicator already started.');
}
$this->message = $message;
$this->started = true;
$this->startTime = time();
$this->indicatorUpdateTime = $this->getCurrentTimeInMilliseconds() + $this->indicatorChangeInterval;
$this->indicatorCurrent = 0;
$this->display();
}
/**
* Advances the indicator.
*/
public function advance()
{
if (!$this->started) {
throw new LogicException('Progress indicator has not yet been started.');
}
if (!$this->output->isDecorated()) {
return;
}
$currentTime = $this->getCurrentTimeInMilliseconds();
if ($currentTime < $this->indicatorUpdateTime) {
return;
}
$this->indicatorUpdateTime = $currentTime + $this->indicatorChangeInterval;
++$this->indicatorCurrent;
$this->display();
}
/**
* Finish the indicator with message.
*
* @param $message
*/
public function finish(string $message)
{
if (!$this->started) {
throw new LogicException('Progress indicator has not yet been started.');
}
$this->message = $message;
$this->display();
$this->output->writeln('');
$this->started = false;
}
/**
* Gets the format for a given name.
*
* @return string|null A format string
*/
public static function getFormatDefinition(string $name)
{
if (!self::$formats) {
self::$formats = self::initFormats();
}
return isset(self::$formats[$name]) ? self::$formats[$name] : null;
}
/**
* Sets a placeholder formatter for a given name.
*
* This method also allow you to override an existing placeholder.
*/
public static function setPlaceholderFormatterDefinition(string $name, callable $callable)
{
if (!self::$formatters) {
self::$formatters = self::initPlaceholderFormatters();
}
self::$formatters[$name] = $callable;
}
/**
* Gets the placeholder formatter for a given name (including the delimiter char like %).
*
* @return callable|null A PHP callable
*/
public static function getPlaceholderFormatterDefinition(string $name)
{
if (!self::$formatters) {
self::$formatters = self::initPlaceholderFormatters();
}
return isset(self::$formatters[$name]) ? self::$formatters[$name] : null;
}
private function display()
{
if (OutputInterface::VERBOSITY_QUIET === $this->output->getVerbosity()) {
return;
}
$this->overwrite(preg_replace_callback("{%([a-z\-_]+)(?:\:([^%]+))?%}i", function ($matches) {
if ($formatter = self::getPlaceholderFormatterDefinition($matches[1])) {
return $formatter($this);
}
return $matches[0];
}, $this->format));
}
private function determineBestFormat(): string
{
switch ($this->output->getVerbosity()) {
// OutputInterface::VERBOSITY_QUIET: display is disabled anyway
case OutputInterface::VERBOSITY_VERBOSE:
return $this->output->isDecorated() ? 'verbose' : 'verbose_no_ansi';
case OutputInterface::VERBOSITY_VERY_VERBOSE:
case OutputInterface::VERBOSITY_DEBUG:
return $this->output->isDecorated() ? 'very_verbose' : 'very_verbose_no_ansi';
default:
return $this->output->isDecorated() ? 'normal' : 'normal_no_ansi';
}
}
/**
* Overwrites a previous message to the output.
*/
private function overwrite(string $message)
{
if ($this->output->isDecorated()) {
$this->output->write("\x0D\x1B[2K");
$this->output->write($message);
} else {
$this->output->writeln($message);
}
}
private function getCurrentTimeInMilliseconds(): float
{
return round(microtime(true) * 1000);
}
private static function initPlaceholderFormatters(): array
{
return [
'indicator' => function (self $indicator) {
return $indicator->indicatorValues[$indicator->indicatorCurrent % \count($indicator->indicatorValues)];
},
'message' => function (self $indicator) {
return $indicator->message;
},
'elapsed' => function (self $indicator) {
return Helper::formatTime(time() - $indicator->startTime);
},
'memory' => function () {
return Helper::formatMemory(memory_get_usage(true));
},
];
}
private static function initFormats(): array
{
return [
'normal' => ' %indicator% %message%',
'normal_no_ansi' => ' %message%',
'verbose' => ' %indicator% %message% (%elapsed:6s%)',
'verbose_no_ansi' => ' %message% (%elapsed:6s%)',
'very_verbose' => ' %indicator% %message% (%elapsed:6s%, %memory:6s%)',
'very_verbose_no_ansi' => ' %message% (%elapsed:6s%, %memory:6s%)',
];
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Helper;
use Symfony\Component\Console\Exception\InvalidArgumentException;
/**
* @author Abdellatif Ait boudad <a.aitboudad@gmail.com>
*/
class TableCell
{
private $value;
private $options = [
'rowspan' => 1,
'colspan' => 1,
'style' => null,
];
public function __construct(string $value = '', array $options = [])
{
$this->value = $value;
// check option names
if ($diff = array_diff(array_keys($options), array_keys($this->options))) {
throw new InvalidArgumentException(sprintf('The TableCell does not support the following options: \'%s\'.', implode('\', \'', $diff)));
}
if (isset($options['style']) && !$options['style'] instanceof TableCellStyle) {
throw new InvalidArgumentException('The style option must be an instance of "TableCellStyle".');
}
$this->options = array_merge($this->options, $options);
}
/**
* Returns the cell value.
*
* @return string
*/
public function __toString()
{
return $this->value;
}
/**
* Gets number of colspan.
*
* @return int
*/
public function getColspan()
{
return (int) $this->options['colspan'];
}
/**
* Gets number of rowspan.
*
* @return int
*/
public function getRowspan()
{
return (int) $this->options['rowspan'];
}
public function getStyle(): ?TableCellStyle
{
return $this->options['style'];
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Helper;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\InvalidArgumentException;
/**
* HelperSet represents a set of helpers to be used with a command.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class HelperSet implements \IteratorAggregate
{
/**
* @var Helper[]
*/
private $helpers = [];
private $command;
/**
* @param Helper[] $helpers An array of helper
*/
public function __construct(array $helpers = [])
{
foreach ($helpers as $alias => $helper) {
$this->set($helper, \is_int($alias) ? null : $alias);
}
}
public function set(HelperInterface $helper, string $alias = null)
{
$this->helpers[$helper->getName()] = $helper;
if (null !== $alias) {
$this->helpers[$alias] = $helper;
}
$helper->setHelperSet($this);
}
/**
* Returns true if the helper if defined.
*
* @return bool true if the helper is defined, false otherwise
*/
public function has(string $name)
{
return isset($this->helpers[$name]);
}
/**
* Gets a helper value.
*
* @return HelperInterface The helper instance
*
* @throws InvalidArgumentException if the helper is not defined
*/
public function get(string $name)
{
if (!$this->has($name)) {
throw new InvalidArgumentException(sprintf('The helper "%s" is not defined.', $name));
}
return $this->helpers[$name];
}
public function setCommand(Command $command = null)
{
$this->command = $command;
}
/**
* Gets the command associated with this helper set.
*
* @return Command A Command instance
*/
public function getCommand()
{
return $this->command;
}
/**
* @return Helper[]
*/
public function getIterator()
{
return new \ArrayIterator($this->helpers);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Helper;
use Symfony\Component\Console\Exception\InvalidArgumentException;
/**
* @author Yewhen Khoptynskyi <khoptynskyi@gmail.com>
*/
class TableCellStyle
{
const DEFAULT_ALIGN = 'left';
private $options = [
'fg' => 'default',
'bg' => 'default',
'options' => null,
'align' => self::DEFAULT_ALIGN,
'cellFormat' => null,
];
private $tagOptions = [
'fg',
'bg',
'options',
];
private $alignMap = [
'left' => \STR_PAD_RIGHT,
'center' => \STR_PAD_BOTH,
'right' => \STR_PAD_LEFT,
];
public function __construct(array $options = [])
{
if ($diff = array_diff(array_keys($options), array_keys($this->options))) {
throw new InvalidArgumentException(sprintf('The TableCellStyle does not support the following options: \'%s\'.', implode('\', \'', $diff)));
}
if (isset($options['align']) && !\array_key_exists($options['align'], $this->alignMap)) {
throw new InvalidArgumentException(sprintf('Wrong align value. Value must be following: \'%s\'.', implode('\', \'', array_keys($this->alignMap))));
}
$this->options = array_merge($this->options, $options);
}
public function getOptions(): array
{
return $this->options;
}
/**
* Gets options we need for tag for example fg, bg.
*
* @return string[]
*/
public function getTagOptions()
{
return array_filter(
$this->getOptions(),
function ($key) {
return \in_array($key, $this->tagOptions) && isset($this->options[$key]);
},
\ARRAY_FILTER_USE_KEY
);
}
public function getPadByAlign()
{
return $this->alignMap[$this->getOptions()['align']];
}
public function getCellFormat(): ?string
{
return $this->getOptions()['cellFormat'];
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Helper;
use Symfony\Component\Console\Formatter\OutputFormatterInterface;
/**
* Helper is the base class for all helper classes.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
abstract class Helper implements HelperInterface
{
protected $helperSet = null;
/**
* {@inheritdoc}
*/
public function setHelperSet(HelperSet $helperSet = null)
{
$this->helperSet = $helperSet;
}
/**
* {@inheritdoc}
*/
public function getHelperSet()
{
return $this->helperSet;
}
/**
* Returns the length of a string, using mb_strwidth if it is available.
*
* @return int The length of the string
*/
public static function strlen(?string $string)
{
if (false === $encoding = mb_detect_encoding($string, null, true)) {
return \strlen($string);
}
return mb_strwidth($string, $encoding);
}
/**
* Returns the subset of a string, using mb_substr if it is available.
*
* @return string The string subset
*/
public static function substr(string $string, int $from, int $length = null)
{
if (false === $encoding = mb_detect_encoding($string, null, true)) {
return substr($string, $from, $length);
}
return mb_substr($string, $from, $length, $encoding);
}
public static function formatTime($secs)
{
static $timeFormats = [
[0, '< 1 sec'],
[1, '1 sec'],
[2, 'secs', 1],
[60, '1 min'],
[120, 'mins', 60],
[3600, '1 hr'],
[7200, 'hrs', 3600],
[86400, '1 day'],
[172800, 'days', 86400],
];
foreach ($timeFormats as $index => $format) {
if ($secs >= $format[0]) {
if ((isset($timeFormats[$index + 1]) && $secs < $timeFormats[$index + 1][0])
|| $index == \count($timeFormats) - 1
) {
if (2 == \count($format)) {
return $format[1];
}
return floor($secs / $format[2]).' '.$format[1];
}
}
}
}
public static function formatMemory(int $memory)
{
if ($memory >= 1024 * 1024 * 1024) {
return sprintf('%.1f GiB', $memory / 1024 / 1024 / 1024);
}
if ($memory >= 1024 * 1024) {
return sprintf('%.1f MiB', $memory / 1024 / 1024);
}
if ($memory >= 1024) {
return sprintf('%d KiB', $memory / 1024);
}
return sprintf('%d B', $memory);
}
public static function strlenWithoutDecoration(OutputFormatterInterface $formatter, $string)
{
return self::strlen(self::removeDecoration($formatter, $string));
}
public static function removeDecoration(OutputFormatterInterface $formatter, $string)
{
$isDecorated = $formatter->isDecorated();
$formatter->setDecorated(false);
// remove <...> formatting
$string = $formatter->format($string);
// remove already formatted characters
$string = preg_replace("/\033\[[^m]*m/", '', $string);
$formatter->setDecorated($isDecorated);
return $string;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Helper;
use Symfony\Component\Console\Cursor;
use Symfony\Component\Console\Exception\MissingInputException;
use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\StreamableInputInterface;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\ConsoleSectionOutput;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ChoiceQuestion;
use Symfony\Component\Console\Question\Question;
use Symfony\Component\Console\Terminal;
use function Symfony\Component\String\s;
/**
* The QuestionHelper class provides helpers to interact with the user.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class QuestionHelper extends Helper
{
private $inputStream;
private static $shell;
private static $stty = true;
private static $stdinIsInteractive;
/**
* Asks a question to the user.
*
* @return mixed The user answer
*
* @throws RuntimeException If there is no data to read in the input stream
*/
public function ask(InputInterface $input, OutputInterface $output, Question $question)
{
if ($output instanceof ConsoleOutputInterface) {
$output = $output->getErrorOutput();
}
if (!$input->isInteractive()) {
return $this->getDefaultAnswer($question);
}
if ($input instanceof StreamableInputInterface && $stream = $input->getStream()) {
$this->inputStream = $stream;
}
try {
if (!$question->getValidator()) {
return $this->doAsk($output, $question);
}
$interviewer = function () use ($output, $question) {
return $this->doAsk($output, $question);
};
return $this->validateAttempts($interviewer, $output, $question);
} catch (MissingInputException $exception) {
$input->setInteractive(false);
if (null === $fallbackOutput = $this->getDefaultAnswer($question)) {
throw $exception;
}
return $fallbackOutput;
}
}
/**
* {@inheritdoc}
*/
public function getName()
{
return 'question';
}
/**
* Prevents usage of stty.
*/
public static function disableStty()
{
self::$stty = false;
}
/**
* Asks the question to the user.
*
* @return bool|mixed|string|null
*
* @throws RuntimeException In case the fallback is deactivated and the response cannot be hidden
*/
private function doAsk(OutputInterface $output, Question $question)
{
$this->writePrompt($output, $question);
$inputStream = $this->inputStream ?: \STDIN;
$autocomplete = $question->getAutocompleterCallback();
if (\function_exists('sapi_windows_cp_set')) {
// Codepage used by cmd.exe on Windows to allow special characters (éàüñ).
@sapi_windows_cp_set(1252);
}
if (null === $autocomplete || !self::$stty || !Terminal::hasSttyAvailable()) {
$ret = false;
if ($question->isHidden()) {
try {
$hiddenResponse = $this->getHiddenResponse($output, $inputStream, $question->isTrimmable());
$ret = $question->isTrimmable() ? trim($hiddenResponse) : $hiddenResponse;
} catch (RuntimeException $e) {
if (!$question->isHiddenFallback()) {
throw $e;
}
}
}
if (false === $ret) {
$ret = $this->readInput($inputStream, $question);
if (false === $ret) {
throw new MissingInputException('Aborted.');
}
if ($question->isTrimmable()) {
$ret = trim($ret);
}
}
} else {
$autocomplete = $this->autocomplete($output, $question, $inputStream, $autocomplete);
$ret = $question->isTrimmable() ? trim($autocomplete) : $autocomplete;
}
if ($output instanceof ConsoleSectionOutput) {
$output->addContent($ret);
}
$ret = \strlen($ret) > 0 ? $ret : $question->getDefault();
if ($normalizer = $question->getNormalizer()) {
return $normalizer($ret);
}
return $ret;
}
/**
* @return mixed
*/
private function getDefaultAnswer(Question $question)
{
$default = $question->getDefault();
if (null === $default) {
return $default;
}
if ($validator = $question->getValidator()) {
return \call_user_func($question->getValidator(), $default);
} elseif ($question instanceof ChoiceQuestion) {
$choices = $question->getChoices();
if (!$question->isMultiselect()) {
return isset($choices[$default]) ? $choices[$default] : $default;
}
$default = explode(',', $default);
foreach ($default as $k => $v) {
$v = $question->isTrimmable() ? trim($v) : $v;
$default[$k] = isset($choices[$v]) ? $choices[$v] : $v;
}
}
return $default;
}
/**
* Outputs the question prompt.
*/
protected function writePrompt(OutputInterface $output, Question $question)
{
$message = $question->getQuestion();
if ($question instanceof ChoiceQuestion) {
$output->writeln(array_merge([
$question->getQuestion(),
], $this->formatChoiceQuestionChoices($question, 'info')));
$message = $question->getPrompt();
}
$output->write($message);
}
/**
* @return string[]
*/
protected function formatChoiceQuestionChoices(ChoiceQuestion $question, string $tag)
{
$messages = [];
$maxWidth = max(array_map('self::strlen', array_keys($choices = $question->getChoices())));
foreach ($choices as $key => $value) {
$padding = str_repeat(' ', $maxWidth - self::strlen($key));
$messages[] = sprintf(" [<$tag>%s$padding</$tag>] %s", $key, $value);
}
return $messages;
}
/**
* Outputs an error message.
*/
protected function writeError(OutputInterface $output, \Exception $error)
{
if (null !== $this->getHelperSet() && $this->getHelperSet()->has('formatter')) {
$message = $this->getHelperSet()->get('formatter')->formatBlock($error->getMessage(), 'error');
} else {
$message = '<error>'.$error->getMessage().'</error>';
}
$output->writeln($message);
}
/**
* Autocompletes a question.
*
* @param resource $inputStream
*/
private function autocomplete(OutputInterface $output, Question $question, $inputStream, callable $autocomplete): string
{
$cursor = new Cursor($output, $inputStream);
$fullChoice = '';
$ret = '';
$i = 0;
$ofs = -1;
$matches = $autocomplete($ret);
$numMatches = \count($matches);
$sttyMode = shell_exec('stty -g');
// Disable icanon (so we can fread each keypress) and echo (we'll do echoing here instead)
shell_exec('stty -icanon -echo');
// Add highlighted text style
$output->getFormatter()->setStyle('hl', new OutputFormatterStyle('black', 'white'));
// Read a keypress
while (!feof($inputStream)) {
$c = fread($inputStream, 1);
// as opposed to fgets(), fread() returns an empty string when the stream content is empty, not false.
if (false === $c || ('' === $ret && '' === $c && null === $question->getDefault())) {
shell_exec(sprintf('stty %s', $sttyMode));
throw new MissingInputException('Aborted.');
} elseif ("\177" === $c) { // Backspace Character
if (0 === $numMatches && 0 !== $i) {
--$i;
$cursor->moveLeft(s($fullChoice)->slice(-1)->width(false));
$fullChoice = self::substr($fullChoice, 0, $i);
}
if (0 === $i) {
$ofs = -1;
$matches = $autocomplete($ret);
$numMatches = \count($matches);
} else {
$numMatches = 0;
}
// Pop the last character off the end of our string
$ret = self::substr($ret, 0, $i);
} elseif ("\033" === $c) {
// Did we read an escape sequence?
$c .= fread($inputStream, 2);
// A = Up Arrow. B = Down Arrow
if (isset($c[2]) && ('A' === $c[2] || 'B' === $c[2])) {
if ('A' === $c[2] && -1 === $ofs) {
$ofs = 0;
}
if (0 === $numMatches) {
continue;
}
$ofs += ('A' === $c[2]) ? -1 : 1;
$ofs = ($numMatches + $ofs) % $numMatches;
}
} elseif (\ord($c) < 32) {
if ("\t" === $c || "\n" === $c) {
if ($numMatches > 0 && -1 !== $ofs) {
$ret = (string) $matches[$ofs];
// Echo out remaining chars for current match
$remainingCharacters = substr($ret, \strlen(trim($this->mostRecentlyEnteredValue($fullChoice))));
$output->write($remainingCharacters);
$fullChoice .= $remainingCharacters;
$i = self::strlen($fullChoice);
$matches = array_filter(
$autocomplete($ret),
function ($match) use ($ret) {
return '' === $ret || 0 === strpos($match, $ret);
}
);
$numMatches = \count($matches);
$ofs = -1;
}
if ("\n" === $c) {
$output->write($c);
break;
}
$numMatches = 0;
}
continue;
} else {
if ("\x80" <= $c) {
$c .= fread($inputStream, ["\xC0" => 1, "\xD0" => 1, "\xE0" => 2, "\xF0" => 3][$c & "\xF0"]);
}
$output->write($c);
$ret .= $c;
$fullChoice .= $c;
++$i;
$tempRet = $ret;
if ($question instanceof ChoiceQuestion && $question->isMultiselect()) {
$tempRet = $this->mostRecentlyEnteredValue($fullChoice);
}
$numMatches = 0;
$ofs = 0;
foreach ($autocomplete($ret) as $value) {
// If typed characters match the beginning chunk of value (e.g. [AcmeDe]moBundle)
if (0 === strpos($value, $tempRet)) {
$matches[$numMatches++] = $value;
}
}
}
$cursor->clearLineAfter();
if ($numMatches > 0 && -1 !== $ofs) {
$cursor->savePosition();
// Write highlighted text, complete the partially entered response
$charactersEntered = \strlen(trim($this->mostRecentlyEnteredValue($fullChoice)));
$output->write('<hl>'.OutputFormatter::escapeTrailingBackslash(substr($matches[$ofs], $charactersEntered)).'</hl>');
$cursor->restorePosition();
}
}
// Reset stty so it behaves normally again
shell_exec(sprintf('stty %s', $sttyMode));
return $fullChoice;
}
private function mostRecentlyEnteredValue(string $entered): string
{
// Determine the most recent value that the user entered
if (false === strpos($entered, ',')) {
return $entered;
}
$choices = explode(',', $entered);
if (\strlen($lastChoice = trim($choices[\count($choices) - 1])) > 0) {
return $lastChoice;
}
return $entered;
}
/**
* Gets a hidden response from user.
*
* @param resource $inputStream The handler resource
* @param bool $trimmable Is the answer trimmable
*
* @throws RuntimeException In case the fallback is deactivated and the response cannot be hidden
*/
private function getHiddenResponse(OutputInterface $output, $inputStream, bool $trimmable = true): string
{
if ('\\' === \DIRECTORY_SEPARATOR) {
$exe = __DIR__.'/../Resources/bin/hiddeninput.exe';
// handle code running from a phar
if ('phar:' === substr(__FILE__, 0, 5)) {
$tmpExe = sys_get_temp_dir().'/hiddeninput.exe';
copy($exe, $tmpExe);
$exe = $tmpExe;
}
$sExec = shell_exec($exe);
$value = $trimmable ? rtrim($sExec) : $sExec;
$output->writeln('');
if (isset($tmpExe)) {
unlink($tmpExe);
}
return $value;
}
if (self::$stty && Terminal::hasSttyAvailable()) {
$sttyMode = shell_exec('stty -g');
shell_exec('stty -echo');
} elseif ($this->isInteractiveInput($inputStream)) {
throw new RuntimeException('Unable to hide the response.');
}
$value = fgets($inputStream, 4096);
if (self::$stty && Terminal::hasSttyAvailable()) {
shell_exec(sprintf('stty %s', $sttyMode));
}
if (false === $value) {
throw new MissingInputException('Aborted.');
}
if ($trimmable) {
$value = trim($value);
}
$output->writeln('');
return $value;
}
/**
* Validates an attempt.
*
* @param callable $interviewer A callable that will ask for a question and return the result
*
* @return mixed The validated response
*
* @throws \Exception In case the max number of attempts has been reached and no valid response has been given
*/
private function validateAttempts(callable $interviewer, OutputInterface $output, Question $question)
{
$error = null;
$attempts = $question->getMaxAttempts();
while (null === $attempts || $attempts--) {
if (null !== $error) {
$this->writeError($output, $error);
}
try {
return $question->getValidator()($interviewer());
} catch (RuntimeException $e) {
throw $e;
} catch (\Exception $error) {
}
}
throw $error;
}
private function isInteractiveInput($inputStream): bool
{
if ('php://stdin' !== (stream_get_meta_data($inputStream)['uri'] ?? null)) {
return false;
}
if (null !== self::$stdinIsInteractive) {
return self::$stdinIsInteractive;
}
if (\function_exists('stream_isatty')) {
return self::$stdinIsInteractive = stream_isatty(fopen('php://stdin', 'r'));
}
if (\function_exists('posix_isatty')) {
return self::$stdinIsInteractive = posix_isatty(fopen('php://stdin', 'r'));
}
if (!\function_exists('exec')) {
return self::$stdinIsInteractive = true;
}
exec('stty 2> /dev/null', $output, $status);
return self::$stdinIsInteractive = 1 !== $status;
}
/**
* Reads one or more lines of input and returns what is read.
*
* @param resource $inputStream The handler resource
* @param Question $question The question being asked
*
* @return string|bool The input received, false in case input could not be read
*/
private function readInput($inputStream, Question $question)
{
if (!$question->isMultiline()) {
return fgets($inputStream, 4096);
}
$multiLineStreamReader = $this->cloneInputStream($inputStream);
if (null === $multiLineStreamReader) {
return false;
}
$ret = '';
while (false !== ($char = fgetc($multiLineStreamReader))) {
if (\PHP_EOL === "{$ret}{$char}") {
break;
}
$ret .= $char;
}
return $ret;
}
/**
* Clones an input stream in order to act on one instance of the same
* stream without affecting the other instance.
*
* @param resource $inputStream The handler resource
*
* @return resource|null The cloned resource, null in case it could not be cloned
*/
private function cloneInputStream($inputStream)
{
$streamMetaData = stream_get_meta_data($inputStream);
$seekable = $streamMetaData['seekable'] ?? false;
$mode = $streamMetaData['mode'] ?? 'rb';
$uri = $streamMetaData['uri'] ?? null;
if (null === $uri) {
return null;
}
$cloneStream = fopen($uri, $mode);
// For seekable and writable streams, add all the same data to the
// cloned stream and then seek to the same offset.
if (true === $seekable && !\in_array($mode, ['r', 'rb', 'rt'])) {
$offset = ftell($inputStream);
rewind($inputStream);
stream_copy_to_stream($inputStream, $cloneStream);
fseek($inputStream, $offset);
fseek($cloneStream, $offset);
}
return $cloneStream;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Helper;
use Symfony\Component\Console\Cursor;
use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\ConsoleSectionOutput;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Terminal;
/**
* The ProgressBar provides helpers to display progress output.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Chris Jones <leeked@gmail.com>
*/
final class ProgressBar
{
private $barWidth = 28;
private $barChar;
private $emptyBarChar = '-';
private $progressChar = '>';
private $format;
private $internalFormat;
private $redrawFreq = 1;
private $writeCount;
private $lastWriteTime;
private $minSecondsBetweenRedraws = 0;
private $maxSecondsBetweenRedraws = 1;
private $output;
private $step = 0;
private $max;
private $startTime;
private $stepWidth;
private $percent = 0.0;
private $formatLineCount;
private $messages = [];
private $overwrite = true;
private $terminal;
private $previousMessage;
private $cursor;
private static $formatters;
private static $formats;
/**
* @param int $max Maximum steps (0 if unknown)
*/
public function __construct(OutputInterface $output, int $max = 0, float $minSecondsBetweenRedraws = 1 / 25)
{
if ($output instanceof ConsoleOutputInterface) {
$output = $output->getErrorOutput();
}
$this->output = $output;
$this->setMaxSteps($max);
$this->terminal = new Terminal();
if (0 < $minSecondsBetweenRedraws) {
$this->redrawFreq = null;
$this->minSecondsBetweenRedraws = $minSecondsBetweenRedraws;
}
if (!$this->output->isDecorated()) {
// disable overwrite when output does not support ANSI codes.
$this->overwrite = false;
// set a reasonable redraw frequency so output isn't flooded
$this->redrawFreq = null;
}
$this->startTime = time();
$this->cursor = new Cursor($output);
}
/**
* Sets a placeholder formatter for a given name.
*
* This method also allow you to override an existing placeholder.
*
* @param string $name The placeholder name (including the delimiter char like %)
* @param callable $callable A PHP callable
*/
public static function setPlaceholderFormatterDefinition(string $name, callable $callable): void
{
if (!self::$formatters) {
self::$formatters = self::initPlaceholderFormatters();
}
self::$formatters[$name] = $callable;
}
/**
* Gets the placeholder formatter for a given name.
*
* @param string $name The placeholder name (including the delimiter char like %)
*
* @return callable|null A PHP callable
*/
public static function getPlaceholderFormatterDefinition(string $name): ?callable
{
if (!self::$formatters) {
self::$formatters = self::initPlaceholderFormatters();
}
return isset(self::$formatters[$name]) ? self::$formatters[$name] : null;
}
/**
* Sets a format for a given name.
*
* This method also allow you to override an existing format.
*
* @param string $name The format name
* @param string $format A format string
*/
public static function setFormatDefinition(string $name, string $format): void
{
if (!self::$formats) {
self::$formats = self::initFormats();
}
self::$formats[$name] = $format;
}
/**
* Gets the format for a given name.
*
* @param string $name The format name
*
* @return string|null A format string
*/
public static function getFormatDefinition(string $name): ?string
{
if (!self::$formats) {
self::$formats = self::initFormats();
}
return isset(self::$formats[$name]) ? self::$formats[$name] : null;
}
/**
* Associates a text with a named placeholder.
*
* The text is displayed when the progress bar is rendered but only
* when the corresponding placeholder is part of the custom format line
* (by wrapping the name with %).
*
* @param string $message The text to associate with the placeholder
* @param string $name The name of the placeholder
*/
public function setMessage(string $message, string $name = 'message')
{
$this->messages[$name] = $message;
}
public function getMessage(string $name = 'message')
{
return $this->messages[$name];
}
public function getStartTime(): int
{
return $this->startTime;
}
public function getMaxSteps(): int
{
return $this->max;
}
public function getProgress(): int
{
return $this->step;
}
private function getStepWidth(): int
{
return $this->stepWidth;
}
public function getProgressPercent(): float
{
return $this->percent;
}
public function getBarOffset(): float
{
return floor($this->max ? $this->percent * $this->barWidth : (null === $this->redrawFreq ? min(5, $this->barWidth / 15) * $this->writeCount : $this->step) % $this->barWidth);
}
public function getEstimated(): float
{
if (!$this->step) {
return 0;
}
return round((time() - $this->startTime) / $this->step * $this->max);
}
public function getRemaining(): float
{
if (!$this->step) {
return 0;
}
return round((time() - $this->startTime) / $this->step * ($this->max - $this->step));
}
public function setBarWidth(int $size)
{
$this->barWidth = max(1, $size);
}
public function getBarWidth(): int
{
return $this->barWidth;
}
public function setBarCharacter(string $char)
{
$this->barChar = $char;
}
public function getBarCharacter(): string
{
if (null === $this->barChar) {
return $this->max ? '=' : $this->emptyBarChar;
}
return $this->barChar;
}
public function setEmptyBarCharacter(string $char)
{
$this->emptyBarChar = $char;
}
public function getEmptyBarCharacter(): string
{
return $this->emptyBarChar;
}
public function setProgressCharacter(string $char)
{
$this->progressChar = $char;
}
public function getProgressCharacter(): string
{
return $this->progressChar;
}
public function setFormat(string $format)
{
$this->format = null;
$this->internalFormat = $format;
}
/**
* Sets the redraw frequency.
*
* @param int|float $freq The frequency in steps
*/
public function setRedrawFrequency(?int $freq)
{
$this->redrawFreq = null !== $freq ? max(1, $freq) : null;
}
public function minSecondsBetweenRedraws(float $seconds): void
{
$this->minSecondsBetweenRedraws = $seconds;
}
public function maxSecondsBetweenRedraws(float $seconds): void
{
$this->maxSecondsBetweenRedraws = $seconds;
}
/**
* Returns an iterator that will automatically update the progress bar when iterated.
*
* @param int|null $max Number of steps to complete the bar (0 if indeterminate), if null it will be inferred from $iterable
*/
public function iterate(iterable $iterable, int $max = null): iterable
{
$this->start($max ?? (is_countable($iterable) ? \count($iterable) : 0));
foreach ($iterable as $key => $value) {
yield $key => $value;
$this->advance();
}
$this->finish();
}
/**
* Starts the progress output.
*
* @param int|null $max Number of steps to complete the bar (0 if indeterminate), null to leave unchanged
*/
public function start(int $max = null)
{
$this->startTime = time();
$this->step = 0;
$this->percent = 0.0;
if (null !== $max) {
$this->setMaxSteps($max);
}
$this->display();
}
/**
* Advances the progress output X steps.
*
* @param int $step Number of steps to advance
*/
public function advance(int $step = 1)
{
$this->setProgress($this->step + $step);
}
/**
* Sets whether to overwrite the progressbar, false for new line.
*/
public function setOverwrite(bool $overwrite)
{
$this->overwrite = $overwrite;
}
public function setProgress(int $step)
{
if ($this->max && $step > $this->max) {
$this->max = $step;
} elseif ($step < 0) {
$step = 0;
}
$redrawFreq = $this->redrawFreq ?? (($this->max ?: 10) / 10);
$prevPeriod = (int) ($this->step / $redrawFreq);
$currPeriod = (int) ($step / $redrawFreq);
$this->step = $step;
$this->percent = $this->max ? (float) $this->step / $this->max : 0;
$timeInterval = microtime(true) - $this->lastWriteTime;
// Draw regardless of other limits
if ($this->max === $step) {
$this->display();
return;
}
// Throttling
if ($timeInterval < $this->minSecondsBetweenRedraws) {
return;
}
// Draw each step period, but not too late
if ($prevPeriod !== $currPeriod || $timeInterval >= $this->maxSecondsBetweenRedraws) {
$this->display();
}
}
public function setMaxSteps(int $max)
{
$this->format = null;
$this->max = max(0, $max);
$this->stepWidth = $this->max ? Helper::strlen((string) $this->max) : 4;
}
/**
* Finishes the progress output.
*/
public function finish(): void
{
if (!$this->max) {
$this->max = $this->step;
}
if ($this->step === $this->max && !$this->overwrite) {
// prevent double 100% output
return;
}
$this->setProgress($this->max);
}
/**
* Outputs the current progress string.
*/
public function display(): void
{
if (OutputInterface::VERBOSITY_QUIET === $this->output->getVerbosity()) {
return;
}
if (null === $this->format) {
$this->setRealFormat($this->internalFormat ?: $this->determineBestFormat());
}
$this->overwrite($this->buildLine());
}
/**
* Removes the progress bar from the current line.
*
* This is useful if you wish to write some output
* while a progress bar is running.
* Call display() to show the progress bar again.
*/
public function clear(): void
{
if (!$this->overwrite) {
return;
}
if (null === $this->format) {
$this->setRealFormat($this->internalFormat ?: $this->determineBestFormat());
}
$this->overwrite('');
}
private function setRealFormat(string $format)
{
// try to use the _nomax variant if available
if (!$this->max && null !== self::getFormatDefinition($format.'_nomax')) {
$this->format = self::getFormatDefinition($format.'_nomax');
} elseif (null !== self::getFormatDefinition($format)) {
$this->format = self::getFormatDefinition($format);
} else {
$this->format = $format;
}
$this->formatLineCount = substr_count($this->format, "\n");
}
/**
* Overwrites a previous message to the output.
*/
private function overwrite(string $message): void
{
if ($this->previousMessage === $message) {
return;
}
$originalMessage = $message;
if ($this->overwrite) {
if (null !== $this->previousMessage) {
if ($this->output instanceof ConsoleSectionOutput) {
$lines = floor(Helper::strlen($message) / $this->terminal->getWidth()) + $this->formatLineCount + 1;
$this->output->clear($lines);
} else {
if ($this->formatLineCount > 0) {
$this->cursor->moveUp($this->formatLineCount);
}
$this->cursor->moveToColumn(1);
$this->cursor->clearLine();
}
}
} elseif ($this->step > 0) {
$message = \PHP_EOL.$message;
}
$this->previousMessage = $originalMessage;
$this->lastWriteTime = microtime(true);
$this->output->write($message);
++$this->writeCount;
}
private function determineBestFormat(): string
{
switch ($this->output->getVerbosity()) {
// OutputInterface::VERBOSITY_QUIET: display is disabled anyway
case OutputInterface::VERBOSITY_VERBOSE:
return $this->max ? 'verbose' : 'verbose_nomax';
case OutputInterface::VERBOSITY_VERY_VERBOSE:
return $this->max ? 'very_verbose' : 'very_verbose_nomax';
case OutputInterface::VERBOSITY_DEBUG:
return $this->max ? 'debug' : 'debug_nomax';
default:
return $this->max ? 'normal' : 'normal_nomax';
}
}
private static function initPlaceholderFormatters(): array
{
return [
'bar' => function (self $bar, OutputInterface $output) {
$completeBars = $bar->getBarOffset();
$display = str_repeat($bar->getBarCharacter(), $completeBars);
if ($completeBars < $bar->getBarWidth()) {
$emptyBars = $bar->getBarWidth() - $completeBars - Helper::strlenWithoutDecoration($output->getFormatter(), $bar->getProgressCharacter());
$display .= $bar->getProgressCharacter().str_repeat($bar->getEmptyBarCharacter(), $emptyBars);
}
return $display;
},
'elapsed' => function (self $bar) {
return Helper::formatTime(time() - $bar->getStartTime());
},
'remaining' => function (self $bar) {
if (!$bar->getMaxSteps()) {
throw new LogicException('Unable to display the remaining time if the maximum number of steps is not set.');
}
return Helper::formatTime($bar->getRemaining());
},
'estimated' => function (self $bar) {
if (!$bar->getMaxSteps()) {
throw new LogicException('Unable to display the estimated time if the maximum number of steps is not set.');
}
return Helper::formatTime($bar->getEstimated());
},
'memory' => function (self $bar) {
return Helper::formatMemory(memory_get_usage(true));
},
'current' => function (self $bar) {
return str_pad($bar->getProgress(), $bar->getStepWidth(), ' ', \STR_PAD_LEFT);
},
'max' => function (self $bar) {
return $bar->getMaxSteps();
},
'percent' => function (self $bar) {
return floor($bar->getProgressPercent() * 100);
},
];
}
private static function initFormats(): array
{
return [
'normal' => ' %current%/%max% [%bar%] %percent:3s%%',
'normal_nomax' => ' %current% [%bar%]',
'verbose' => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%',
'verbose_nomax' => ' %current% [%bar%] %elapsed:6s%',
'very_verbose' => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s%',
'very_verbose_nomax' => ' %current% [%bar%] %elapsed:6s%',
'debug' => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s% %memory:6s%',
'debug_nomax' => ' %current% [%bar%] %elapsed:6s% %memory:6s%',
];
}
private function buildLine(): string
{
$regex = "{%([a-z\-_]+)(?:\:([^%]+))?%}i";
$callback = function ($matches) {
if ($formatter = $this::getPlaceholderFormatterDefinition($matches[1])) {
$text = $formatter($this, $this->output);
} elseif (isset($this->messages[$matches[1]])) {
$text = $this->messages[$matches[1]];
} else {
return $matches[0];
}
if (isset($matches[2])) {
$text = sprintf('%'.$matches[2], $text);
}
return $text;
};
$line = preg_replace_callback($regex, $callback, $this->format);
// gets string length for each sub line with multiline format
$linesLength = array_map(function ($subLine) {
return Helper::strlenWithoutDecoration($this->output->getFormatter(), rtrim($subLine, "\r"));
}, explode("\n", $line));
$linesWidth = max($linesLength);
$terminalWidth = $this->terminal->getWidth();
if ($linesWidth <= $terminalWidth) {
return $line;
}
$this->setBarWidth($this->barWidth - $linesWidth + $terminalWidth);
return preg_replace_callback($regex, $callback, $this->format);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Helper;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Formatter\WrappableOutputFormatterInterface;
use Symfony\Component\Console\Output\ConsoleSectionOutput;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Provides helpers to display a table.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Саша Стаменковић <umpirsky@gmail.com>
* @author Abdellatif Ait boudad <a.aitboudad@gmail.com>
* @author Max Grigorian <maxakawizard@gmail.com>
* @author Dany Maillard <danymaillard93b@gmail.com>
*/
class Table
{
private const SEPARATOR_TOP = 0;
private const SEPARATOR_TOP_BOTTOM = 1;
private const SEPARATOR_MID = 2;
private const SEPARATOR_BOTTOM = 3;
private const BORDER_OUTSIDE = 0;
private const BORDER_INSIDE = 1;
private $headerTitle;
private $footerTitle;
/**
* Table headers.
*/
private $headers = [];
/**
* Table rows.
*/
private $rows = [];
private $horizontal = false;
/**
* Column widths cache.
*/
private $effectiveColumnWidths = [];
/**
* Number of columns cache.
*
* @var int
*/
private $numberOfColumns;
/**
* @var OutputInterface
*/
private $output;
/**
* @var TableStyle
*/
private $style;
/**
* @var array
*/
private $columnStyles = [];
/**
* User set column widths.
*
* @var array
*/
private $columnWidths = [];
private $columnMaxWidths = [];
private static $styles;
private $rendered = false;
public function __construct(OutputInterface $output)
{
$this->output = $output;
if (!self::$styles) {
self::$styles = self::initStyles();
}
$this->setStyle('default');
}
/**
* Sets a style definition.
*/
public static function setStyleDefinition(string $name, TableStyle $style)
{
if (!self::$styles) {
self::$styles = self::initStyles();
}
self::$styles[$name] = $style;
}
/**
* Gets a style definition by name.
*
* @return TableStyle
*/
public static function getStyleDefinition(string $name)
{
if (!self::$styles) {
self::$styles = self::initStyles();
}
if (isset(self::$styles[$name])) {
return self::$styles[$name];
}
throw new InvalidArgumentException(sprintf('Style "%s" is not defined.', $name));
}
/**
* Sets table style.
*
* @param TableStyle|string $name The style name or a TableStyle instance
*
* @return $this
*/
public function setStyle($name)
{
$this->style = $this->resolveStyle($name);
return $this;
}
/**
* Gets the current table style.
*
* @return TableStyle
*/
public function getStyle()
{
return $this->style;
}
/**
* Sets table column style.
*
* @param TableStyle|string $name The style name or a TableStyle instance
*
* @return $this
*/
public function setColumnStyle(int $columnIndex, $name)
{
$this->columnStyles[$columnIndex] = $this->resolveStyle($name);
return $this;
}
/**
* Gets the current style for a column.
*
* If style was not set, it returns the global table style.
*
* @return TableStyle
*/
public function getColumnStyle(int $columnIndex)
{
return $this->columnStyles[$columnIndex] ?? $this->getStyle();
}
/**
* Sets the minimum width of a column.
*
* @return $this
*/
public function setColumnWidth(int $columnIndex, int $width)
{
$this->columnWidths[$columnIndex] = $width;
return $this;
}
/**
* Sets the minimum width of all columns.
*
* @return $this
*/
public function setColumnWidths(array $widths)
{
$this->columnWidths = [];
foreach ($widths as $index => $width) {
$this->setColumnWidth($index, $width);
}
return $this;
}
/**
* Sets the maximum width of a column.
*
* Any cell within this column which contents exceeds the specified width will be wrapped into multiple lines, while
* formatted strings are preserved.
*
* @return $this
*/
public function setColumnMaxWidth(int $columnIndex, int $width): self
{
if (!$this->output->getFormatter() instanceof WrappableOutputFormatterInterface) {
throw new \LogicException(sprintf('Setting a maximum column width is only supported when using a "%s" formatter, got "%s".', WrappableOutputFormatterInterface::class, get_debug_type($this->output->getFormatter())));
}
$this->columnMaxWidths[$columnIndex] = $width;
return $this;
}
public function setHeaders(array $headers)
{
$headers = array_values($headers);
if (!empty($headers) && !\is_array($headers[0])) {
$headers = [$headers];
}
$this->headers = $headers;
return $this;
}
public function setRows(array $rows)
{
$this->rows = [];
return $this->addRows($rows);
}
public function addRows(array $rows)
{
foreach ($rows as $row) {
$this->addRow($row);
}
return $this;
}
public function addRow($row)
{
if ($row instanceof TableSeparator) {
$this->rows[] = $row;
return $this;
}
if (!\is_array($row)) {
throw new InvalidArgumentException('A row must be an array or a TableSeparator instance.');
}
$this->rows[] = array_values($row);
return $this;
}
/**
* Adds a row to the table, and re-renders the table.
*/
public function appendRow($row): self
{
if (!$this->output instanceof ConsoleSectionOutput) {
throw new RuntimeException(sprintf('Output should be an instance of "%s" when calling "%s".', ConsoleSectionOutput::class, __METHOD__));
}
if ($this->rendered) {
$this->output->clear($this->calculateRowCount());
}
$this->addRow($row);
$this->render();
return $this;
}
public function setRow($column, array $row)
{
$this->rows[$column] = $row;
return $this;
}
public function setHeaderTitle(?string $title): self
{
$this->headerTitle = $title;
return $this;
}
public function setFooterTitle(?string $title): self
{
$this->footerTitle = $title;
return $this;
}
public function setHorizontal(bool $horizontal = true): self
{
$this->horizontal = $horizontal;
return $this;
}
/**
* Renders table to output.
*
* Example:
*
* +---------------+-----------------------+------------------+
* | ISBN | Title | Author |
* +---------------+-----------------------+------------------+
* | 99921-58-10-7 | Divine Comedy | Dante Alighieri |
* | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens |
* | 960-425-059-0 | The Lord of the Rings | J. R. R. Tolkien |
* +---------------+-----------------------+------------------+
*/
public function render()
{
$divider = new TableSeparator();
if ($this->horizontal) {
$rows = [];
foreach ($this->headers[0] ?? [] as $i => $header) {
$rows[$i] = [$header];
foreach ($this->rows as $row) {
if ($row instanceof TableSeparator) {
continue;
}
if (isset($row[$i])) {
$rows[$i][] = $row[$i];
} elseif ($rows[$i][0] instanceof TableCell && $rows[$i][0]->getColspan() >= 2) {
// Noop, there is a "title"
} else {
$rows[$i][] = null;
}
}
}
} else {
$rows = array_merge($this->headers, [$divider], $this->rows);
}
$this->calculateNumberOfColumns($rows);
$rows = $this->buildTableRows($rows);
$this->calculateColumnsWidth($rows);
$isHeader = !$this->horizontal;
$isFirstRow = $this->horizontal;
foreach ($rows as $row) {
if ($divider === $row) {
$isHeader = false;
$isFirstRow = true;
continue;
}
if ($row instanceof TableSeparator) {
$this->renderRowSeparator();
continue;
}
if (!$row) {
continue;
}
if ($isHeader || $isFirstRow) {
if ($isFirstRow) {
$this->renderRowSeparator(self::SEPARATOR_TOP_BOTTOM);
$isFirstRow = false;
} else {
$this->renderRowSeparator(self::SEPARATOR_TOP, $this->headerTitle, $this->style->getHeaderTitleFormat());
}
}
if ($this->horizontal) {
$this->renderRow($row, $this->style->getCellRowFormat(), $this->style->getCellHeaderFormat());
} else {
$this->renderRow($row, $isHeader ? $this->style->getCellHeaderFormat() : $this->style->getCellRowFormat());
}
}
$this->renderRowSeparator(self::SEPARATOR_BOTTOM, $this->footerTitle, $this->style->getFooterTitleFormat());
$this->cleanup();
$this->rendered = true;
}
/**
* Renders horizontal header separator.
*
* Example:
*
* +-----+-----------+-------+
*/
private function renderRowSeparator(int $type = self::SEPARATOR_MID, string $title = null, string $titleFormat = null)
{
if (0 === $count = $this->numberOfColumns) {
return;
}
$borders = $this->style->getBorderChars();
if (!$borders[0] && !$borders[2] && !$this->style->getCrossingChar()) {
return;
}
$crossings = $this->style->getCrossingChars();
if (self::SEPARATOR_MID === $type) {
[$horizontal, $leftChar, $midChar, $rightChar] = [$borders[2], $crossings[8], $crossings[0], $crossings[4]];
} elseif (self::SEPARATOR_TOP === $type) {
[$horizontal, $leftChar, $midChar, $rightChar] = [$borders[0], $crossings[1], $crossings[2], $crossings[3]];
} elseif (self::SEPARATOR_TOP_BOTTOM === $type) {
[$horizontal, $leftChar, $midChar, $rightChar] = [$borders[0], $crossings[9], $crossings[10], $crossings[11]];
} else {
[$horizontal, $leftChar, $midChar, $rightChar] = [$borders[0], $crossings[7], $crossings[6], $crossings[5]];
}
$markup = $leftChar;
for ($column = 0; $column < $count; ++$column) {
$markup .= str_repeat($horizontal, $this->effectiveColumnWidths[$column]);
$markup .= $column === $count - 1 ? $rightChar : $midChar;
}
if (null !== $title) {
$titleLength = Helper::strlenWithoutDecoration($formatter = $this->output->getFormatter(), $formattedTitle = sprintf($titleFormat, $title));
$markupLength = Helper::strlen($markup);
if ($titleLength > $limit = $markupLength - 4) {
$titleLength = $limit;
$formatLength = Helper::strlenWithoutDecoration($formatter, sprintf($titleFormat, ''));
$formattedTitle = sprintf($titleFormat, Helper::substr($title, 0, $limit - $formatLength - 3).'...');
}
$titleStart = ($markupLength - $titleLength) / 2;
if (false === mb_detect_encoding($markup, null, true)) {
$markup = substr_replace($markup, $formattedTitle, $titleStart, $titleLength);
} else {
$markup = mb_substr($markup, 0, $titleStart).$formattedTitle.mb_substr($markup, $titleStart + $titleLength);
}
}
$this->output->writeln(sprintf($this->style->getBorderFormat(), $markup));
}
/**
* Renders vertical column separator.
*/
private function renderColumnSeparator(int $type = self::BORDER_OUTSIDE): string
{
$borders = $this->style->getBorderChars();
return sprintf($this->style->getBorderFormat(), self::BORDER_OUTSIDE === $type ? $borders[1] : $borders[3]);
}
/**
* Renders table row.
*
* Example:
*
* | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens |
*/
private function renderRow(array $row, string $cellFormat, string $firstCellFormat = null)
{
$rowContent = $this->renderColumnSeparator(self::BORDER_OUTSIDE);
$columns = $this->getRowColumns($row);
$last = \count($columns) - 1;
foreach ($columns as $i => $column) {
if ($firstCellFormat && 0 === $i) {
$rowContent .= $this->renderCell($row, $column, $firstCellFormat);
} else {
$rowContent .= $this->renderCell($row, $column, $cellFormat);
}
$rowContent .= $this->renderColumnSeparator($last === $i ? self::BORDER_OUTSIDE : self::BORDER_INSIDE);
}
$this->output->writeln($rowContent);
}
/**
* Renders table cell with padding.
*/
private function renderCell(array $row, int $column, string $cellFormat): string
{
$cell = isset($row[$column]) ? $row[$column] : '';
$width = $this->effectiveColumnWidths[$column];
if ($cell instanceof TableCell && $cell->getColspan() > 1) {
// add the width of the following columns(numbers of colspan).
foreach (range($column + 1, $column + $cell->getColspan() - 1) as $nextColumn) {
$width += $this->getColumnSeparatorWidth() + $this->effectiveColumnWidths[$nextColumn];
}
}
// str_pad won't work properly with multi-byte strings, we need to fix the padding
if (false !== $encoding = mb_detect_encoding($cell, null, true)) {
$width += \strlen($cell) - mb_strwidth($cell, $encoding);
}
$style = $this->getColumnStyle($column);
if ($cell instanceof TableSeparator) {
return sprintf($style->getBorderFormat(), str_repeat($style->getBorderChars()[2], $width));
}
$width += Helper::strlen($cell) - Helper::strlenWithoutDecoration($this->output->getFormatter(), $cell);
$content = sprintf($style->getCellRowContentFormat(), $cell);
$padType = $style->getPadType();
if ($cell instanceof TableCell && $cell->getStyle() instanceof TableCellStyle) {
$isNotStyledByTag = !preg_match('/^<(\w+|(\w+=[\w,]+;?)*)>.+<\/(\w+|(\w+=\w+;?)*)?>$/', $cell);
if ($isNotStyledByTag) {
$cellFormat = $cell->getStyle()->getCellFormat();
if (!\is_string($cellFormat)) {
$tag = http_build_query($cell->getStyle()->getTagOptions(), null, ';');
$cellFormat = '<'.$tag.'>%s</>';
}
if (strstr($content, '</>')) {
$content = str_replace('</>', '', $content);
$width -= 3;
}
if (strstr($content, '<fg=default;bg=default>')) {
$content = str_replace('<fg=default;bg=default>', '', $content);
$width -= \strlen('<fg=default;bg=default>');
}
}
$padType = $cell->getStyle()->getPadByAlign();
}
return sprintf($cellFormat, str_pad($content, $width, $style->getPaddingChar(), $padType));
}
/**
* Calculate number of columns for this table.
*/
private function calculateNumberOfColumns(array $rows)
{
$columns = [0];
foreach ($rows as $row) {
if ($row instanceof TableSeparator) {
continue;
}
$columns[] = $this->getNumberOfColumns($row);
}
$this->numberOfColumns = max($columns);
}
private function buildTableRows(array $rows): TableRows
{
/** @var WrappableOutputFormatterInterface $formatter */
$formatter = $this->output->getFormatter();
$unmergedRows = [];
for ($rowKey = 0; $rowKey < \count($rows); ++$rowKey) {
$rows = $this->fillNextRows($rows, $rowKey);
// Remove any new line breaks and replace it with a new line
foreach ($rows[$rowKey] as $column => $cell) {
$colspan = $cell instanceof TableCell ? $cell->getColspan() : 1;
if (isset($this->columnMaxWidths[$column]) && Helper::strlenWithoutDecoration($formatter, $cell) > $this->columnMaxWidths[$column]) {
$cell = $formatter->formatAndWrap($cell, $this->columnMaxWidths[$column] * $colspan);
}
if (!strstr($cell, "\n")) {
continue;
}
$escaped = implode("\n", array_map([OutputFormatter::class, 'escapeTrailingBackslash'], explode("\n", $cell)));
$cell = $cell instanceof TableCell ? new TableCell($escaped, ['colspan' => $cell->getColspan()]) : $escaped;
$lines = explode("\n", str_replace("\n", "<fg=default;bg=default>\n</>", $cell));
foreach ($lines as $lineKey => $line) {
if ($colspan > 1) {
$line = new TableCell($line, ['colspan' => $colspan]);
}
if (0 === $lineKey) {
$rows[$rowKey][$column] = $line;
} else {
if (!\array_key_exists($rowKey, $unmergedRows) || !\array_key_exists($lineKey, $unmergedRows[$rowKey])) {
$unmergedRows[$rowKey][$lineKey] = $this->copyRow($rows, $rowKey);
}
$unmergedRows[$rowKey][$lineKey][$column] = $line;
}
}
}
}
return new TableRows(function () use ($rows, $unmergedRows): \Traversable {
foreach ($rows as $rowKey => $row) {
yield $this->fillCells($row);
if (isset($unmergedRows[$rowKey])) {
foreach ($unmergedRows[$rowKey] as $unmergedRow) {
yield $this->fillCells($unmergedRow);
}
}
}
});
}
private function calculateRowCount(): int
{
$numberOfRows = \count(iterator_to_array($this->buildTableRows(array_merge($this->headers, [new TableSeparator()], $this->rows))));
if ($this->headers) {
++$numberOfRows; // Add row for header separator
}
if (\count($this->rows) > 0) {
++$numberOfRows; // Add row for footer separator
}
return $numberOfRows;
}
/**
* fill rows that contains rowspan > 1.
*
* @throws InvalidArgumentException
*/
private function fillNextRows(array $rows, int $line): array
{
$unmergedRows = [];
foreach ($rows[$line] as $column => $cell) {
if (null !== $cell && !$cell instanceof TableCell && !is_scalar($cell) && !(\is_object($cell) && method_exists($cell, '__toString'))) {
throw new InvalidArgumentException(sprintf('A cell must be a TableCell, a scalar or an object implementing "__toString()", "%s" given.', get_debug_type($cell)));
}
if ($cell instanceof TableCell && $cell->getRowspan() > 1) {
$nbLines = $cell->getRowspan() - 1;
$lines = [$cell];
if (strstr($cell, "\n")) {
$lines = explode("\n", str_replace("\n", "<fg=default;bg=default>\n</>", $cell));
$nbLines = \count($lines) > $nbLines ? substr_count($cell, "\n") : $nbLines;
$rows[$line][$column] = new TableCell($lines[0], ['colspan' => $cell->getColspan(), 'style' => $cell->getStyle()]);
unset($lines[0]);
}
// create a two dimensional array (rowspan x colspan)
$unmergedRows = array_replace_recursive(array_fill($line + 1, $nbLines, []), $unmergedRows);
foreach ($unmergedRows as $unmergedRowKey => $unmergedRow) {
$value = isset($lines[$unmergedRowKey - $line]) ? $lines[$unmergedRowKey - $line] : '';
$unmergedRows[$unmergedRowKey][$column] = new TableCell($value, ['colspan' => $cell->getColspan(), 'style' => $cell->getStyle()]);
if ($nbLines === $unmergedRowKey - $line) {
break;
}
}
}
}
foreach ($unmergedRows as $unmergedRowKey => $unmergedRow) {
// we need to know if $unmergedRow will be merged or inserted into $rows
if (isset($rows[$unmergedRowKey]) && \is_array($rows[$unmergedRowKey]) && ($this->getNumberOfColumns($rows[$unmergedRowKey]) + $this->getNumberOfColumns($unmergedRows[$unmergedRowKey]) <= $this->numberOfColumns)) {
foreach ($unmergedRow as $cellKey => $cell) {
// insert cell into row at cellKey position
array_splice($rows[$unmergedRowKey], $cellKey, 0, [$cell]);
}
} else {
$row = $this->copyRow($rows, $unmergedRowKey - 1);
foreach ($unmergedRow as $column => $cell) {
if (!empty($cell)) {
$row[$column] = $unmergedRow[$column];
}
}
array_splice($rows, $unmergedRowKey, 0, [$row]);
}
}
return $rows;
}
/**
* fill cells for a row that contains colspan > 1.
*/
private function fillCells($row)
{
$newRow = [];
foreach ($row as $column => $cell) {
$newRow[] = $cell;
if ($cell instanceof TableCell && $cell->getColspan() > 1) {
foreach (range($column + 1, $column + $cell->getColspan() - 1) as $position) {
// insert empty value at column position
$newRow[] = '';
}
}
}
return $newRow ?: $row;
}
private function copyRow(array $rows, int $line): array
{
$row = $rows[$line];
foreach ($row as $cellKey => $cellValue) {
$row[$cellKey] = '';
if ($cellValue instanceof TableCell) {
$row[$cellKey] = new TableCell('', ['colspan' => $cellValue->getColspan()]);
}
}
return $row;
}
/**
* Gets number of columns by row.
*/
private function getNumberOfColumns(array $row): int
{
$columns = \count($row);
foreach ($row as $column) {
$columns += $column instanceof TableCell ? ($column->getColspan() - 1) : 0;
}
return $columns;
}
/**
* Gets list of columns for the given row.
*/
private function getRowColumns(array $row): array
{
$columns = range(0, $this->numberOfColumns - 1);
foreach ($row as $cellKey => $cell) {
if ($cell instanceof TableCell && $cell->getColspan() > 1) {
// exclude grouped columns.
$columns = array_diff($columns, range($cellKey + 1, $cellKey + $cell->getColspan() - 1));
}
}
return $columns;
}
/**
* Calculates columns widths.
*/
private function calculateColumnsWidth(iterable $rows)
{
for ($column = 0; $column < $this->numberOfColumns; ++$column) {
$lengths = [];
foreach ($rows as $row) {
if ($row instanceof TableSeparator) {
continue;
}
foreach ($row as $i => $cell) {
if ($cell instanceof TableCell) {
$textContent = Helper::removeDecoration($this->output->getFormatter(), $cell);
$textLength = Helper::strlen($textContent);
if ($textLength > 0) {
$contentColumns = str_split($textContent, ceil($textLength / $cell->getColspan()));
foreach ($contentColumns as $position => $content) {
$row[$i + $position] = $content;
}
}
}
}
$lengths[] = $this->getCellWidth($row, $column);
}
$this->effectiveColumnWidths[$column] = max($lengths) + Helper::strlen($this->style->getCellRowContentFormat()) - 2;
}
}
private function getColumnSeparatorWidth(): int
{
return Helper::strlen(sprintf($this->style->getBorderFormat(), $this->style->getBorderChars()[3]));
}
private function getCellWidth(array $row, int $column): int
{
$cellWidth = 0;
if (isset($row[$column])) {
$cell = $row[$column];
$cellWidth = Helper::strlenWithoutDecoration($this->output->getFormatter(), $cell);
}
$columnWidth = isset($this->columnWidths[$column]) ? $this->columnWidths[$column] : 0;
$cellWidth = max($cellWidth, $columnWidth);
return isset($this->columnMaxWidths[$column]) ? min($this->columnMaxWidths[$column], $cellWidth) : $cellWidth;
}
/**
* Called after rendering to cleanup cache data.
*/
private function cleanup()
{
$this->effectiveColumnWidths = [];
$this->numberOfColumns = null;
}
private static function initStyles(): array
{
$borderless = new TableStyle();
$borderless
->setHorizontalBorderChars('=')
->setVerticalBorderChars(' ')
->setDefaultCrossingChar(' ')
;
$compact = new TableStyle();
$compact
->setHorizontalBorderChars('')
->setVerticalBorderChars(' ')
->setDefaultCrossingChar('')
->setCellRowContentFormat('%s')
;
$styleGuide = new TableStyle();
$styleGuide
->setHorizontalBorderChars('-')
->setVerticalBorderChars(' ')
->setDefaultCrossingChar(' ')
->setCellHeaderFormat('%s')
;
$box = (new TableStyle())
->setHorizontalBorderChars('─')
->setVerticalBorderChars('│')
->setCrossingChars('┼', '┌', '┬', '┐', '┤', '┘', '┴', '└', '├')
;
$boxDouble = (new TableStyle())
->setHorizontalBorderChars('═', '─')
->setVerticalBorderChars('║', '│')
->setCrossingChars('┼', '╔', '╤', '╗', '╢', '╝', '╧', '╚', '╟', '╠', '╪', '╣')
;
return [
'default' => new TableStyle(),
'borderless' => $borderless,
'compact' => $compact,
'symfony-style-guide' => $styleGuide,
'box' => $box,
'box-double' => $boxDouble,
];
}
private function resolveStyle($name): TableStyle
{
if ($name instanceof TableStyle) {
return $name;
}
if (isset(self::$styles[$name])) {
return self::$styles[$name];
}
throw new InvalidArgumentException(sprintf('Style "%s" is not defined.', $name));
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* @author Grégoire Pineau <lyrixx@lyrixx.info>
*/
class SingleCommandApplication extends Command
{
private $version = 'UNKNOWN';
private $autoExit = true;
private $running = false;
public function setVersion(string $version): self
{
$this->version = $version;
return $this;
}
/**
* @final
*/
public function setAutoExit(bool $autoExit): self
{
$this->autoExit = $autoExit;
return $this;
}
public function run(InputInterface $input = null, OutputInterface $output = null): int
{
if ($this->running) {
return parent::run($input, $output);
}
// We use the command name as the application name
$application = new Application($this->getName() ?: 'UNKNOWN', $this->version);
$application->setAutoExit($this->autoExit);
// Fix the usage of the command displayed with "--help"
$this->setName($_SERVER['argv'][0]);
$application->add($this);
$application->setDefaultCommand($this->getName(), true);
$this->running = true;
try {
$ret = $application->run($input, $output);
} finally {
$this->running = false;
}
return $ret ?? 1;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\EventListener;
use Psr\Log\LoggerInterface;
use Symfony\Component\Console\ConsoleEvents;
use Symfony\Component\Console\Event\ConsoleErrorEvent;
use Symfony\Component\Console\Event\ConsoleEvent;
use Symfony\Component\Console\Event\ConsoleTerminateEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* @author James Halsall <james.t.halsall@googlemail.com>
* @author Robin Chalas <robin.chalas@gmail.com>
*/
class ErrorListener implements EventSubscriberInterface
{
private $logger;
public function __construct(LoggerInterface $logger = null)
{
$this->logger = $logger;
}
public function onConsoleError(ConsoleErrorEvent $event)
{
if (null === $this->logger) {
return;
}
$error = $event->getError();
if (!$inputString = $this->getInputString($event)) {
$this->logger->error('An error occurred while using the console. Message: "{message}"', ['exception' => $error, 'message' => $error->getMessage()]);
return;
}
$this->logger->error('Error thrown while running command "{command}". Message: "{message}"', ['exception' => $error, 'command' => $inputString, 'message' => $error->getMessage()]);
}
public function onConsoleTerminate(ConsoleTerminateEvent $event)
{
if (null === $this->logger) {
return;
}
$exitCode = $event->getExitCode();
if (0 === $exitCode) {
return;
}
if (!$inputString = $this->getInputString($event)) {
$this->logger->debug('The console exited with code "{code}"', ['code' => $exitCode]);
return;
}
$this->logger->debug('Command "{command}" exited with code "{code}"', ['command' => $inputString, 'code' => $exitCode]);
}
public static function getSubscribedEvents()
{
return [
ConsoleEvents::ERROR => ['onConsoleError', -128],
ConsoleEvents::TERMINATE => ['onConsoleTerminate', -128],
];
}
private static function getInputString(ConsoleEvent $event): ?string
{
$commandName = $event->getCommand() ? $event->getCommand()->getName() : null;
$input = $event->getInput();
if (method_exists($input, '__toString')) {
if ($commandName) {
return str_replace(["'$commandName'", "\"$commandName\""], $commandName, (string) $input);
}
return (string) $input;
}
return $commandName;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Question;
use Symfony\Component\Console\Exception\InvalidArgumentException;
/**
* Represents a choice question.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class ChoiceQuestion extends Question
{
private $choices;
private $multiselect = false;
private $prompt = ' > ';
private $errorMessage = 'Value "%s" is invalid';
/**
* @param string $question The question to ask to the user
* @param array $choices The list of available choices
* @param mixed $default The default answer to return
*/
public function __construct(string $question, array $choices, $default = null)
{
if (!$choices) {
throw new \LogicException('Choice question must have at least 1 choice available.');
}
parent::__construct($question, $default);
$this->choices = $choices;
$this->setValidator($this->getDefaultValidator());
$this->setAutocompleterValues($choices);
}
/**
* Returns available choices.
*
* @return array
*/
public function getChoices()
{
return $this->choices;
}
/**
* Sets multiselect option.
*
* When multiselect is set to true, multiple choices can be answered.
*
* @return $this
*/
public function setMultiselect(bool $multiselect)
{
$this->multiselect = $multiselect;
$this->setValidator($this->getDefaultValidator());
return $this;
}
/**
* Returns whether the choices are multiselect.
*
* @return bool
*/
public function isMultiselect()
{
return $this->multiselect;
}
/**
* Gets the prompt for choices.
*
* @return string
*/
public function getPrompt()
{
return $this->prompt;
}
/**
* Sets the prompt for choices.
*
* @return $this
*/
public function setPrompt(string $prompt)
{
$this->prompt = $prompt;
return $this;
}
/**
* Sets the error message for invalid values.
*
* The error message has a string placeholder (%s) for the invalid value.
*
* @return $this
*/
public function setErrorMessage(string $errorMessage)
{
$this->errorMessage = $errorMessage;
$this->setValidator($this->getDefaultValidator());
return $this;
}
private function getDefaultValidator(): callable
{
$choices = $this->choices;
$errorMessage = $this->errorMessage;
$multiselect = $this->multiselect;
$isAssoc = $this->isAssoc($choices);
return function ($selected) use ($choices, $errorMessage, $multiselect, $isAssoc) {
if ($multiselect) {
// Check for a separated comma values
if (!preg_match('/^[^,]+(?:,[^,]+)*$/', $selected, $matches)) {
throw new InvalidArgumentException(sprintf($errorMessage, $selected));
}
$selectedChoices = explode(',', $selected);
} else {
$selectedChoices = [$selected];
}
if ($this->isTrimmable()) {
foreach ($selectedChoices as $k => $v) {
$selectedChoices[$k] = trim($v);
}
}
$multiselectChoices = [];
foreach ($selectedChoices as $value) {
$results = [];
foreach ($choices as $key => $choice) {
if ($choice === $value) {
$results[] = $key;
}
}
if (\count($results) > 1) {
throw new InvalidArgumentException(sprintf('The provided answer is ambiguous. Value should be one of "%s".', implode('" or "', $results)));
}
$result = array_search($value, $choices);
if (!$isAssoc) {
if (false !== $result) {
$result = $choices[$result];
} elseif (isset($choices[$value])) {
$result = $choices[$value];
}
} elseif (false === $result && isset($choices[$value])) {
$result = $value;
}
if (false === $result) {
throw new InvalidArgumentException(sprintf($errorMessage, $value));
}
// For associative choices, consistently return the key as string:
$multiselectChoices[] = $isAssoc ? (string) $result : $result;
}
if ($multiselect) {
return $multiselectChoices;
}
return current($multiselectChoices);
};
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Question;
/**
* Represents a yes/no question.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class ConfirmationQuestion extends Question
{
private $trueAnswerRegex;
/**
* @param string $question The question to ask to the user
* @param bool $default The default answer to return, true or false
* @param string $trueAnswerRegex A regex to match the "yes" answer
*/
public function __construct(string $question, bool $default = true, string $trueAnswerRegex = '/^y/i')
{
parent::__construct($question, $default);
$this->trueAnswerRegex = $trueAnswerRegex;
$this->setNormalizer($this->getDefaultNormalizer());
}
/**
* Returns the default answer normalizer.
*/
private function getDefaultNormalizer(): callable
{
$default = $this->getDefault();
$regex = $this->trueAnswerRegex;
return function ($answer) use ($default, $regex) {
if (\is_bool($answer)) {
return $answer;
}
$answerIsTrue = (bool) preg_match($regex, $answer);
if (false === $default) {
return $answer && $answerIsTrue;
}
return '' === $answer || $answerIsTrue;
};
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Question;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\LogicException;
/**
* Represents a Question.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class Question
{
private $question;
private $attempts;
private $hidden = false;
private $hiddenFallback = true;
private $autocompleterCallback;
private $validator;
private $default;
private $normalizer;
private $trimmable = true;
private $multiline = false;
/**
* @param string $question The question to ask to the user
* @param mixed $default The default answer to return if the user enters nothing
*/
public function __construct(string $question, $default = null)
{
$this->question = $question;
$this->default = $default;
}
/**
* Returns the question.
*
* @return string
*/
public function getQuestion()
{
return $this->question;
}
/**
* Returns the default answer.
*
* @return mixed
*/
public function getDefault()
{
return $this->default;
}
/**
* Returns whether the user response accepts newline characters.
*/
public function isMultiline(): bool
{
return $this->multiline;
}
/**
* Sets whether the user response should accept newline characters.
*
* @return $this
*/
public function setMultiline(bool $multiline): self
{
$this->multiline = $multiline;
return $this;
}
/**
* Returns whether the user response must be hidden.
*
* @return bool
*/
public function isHidden()
{
return $this->hidden;
}
/**
* Sets whether the user response must be hidden or not.
*
* @param bool $hidden
*
* @return $this
*
* @throws LogicException In case the autocompleter is also used
*/
public function setHidden($hidden)
{
if ($this->autocompleterCallback) {
throw new LogicException('A hidden question cannot use the autocompleter.');
}
$this->hidden = (bool) $hidden;
return $this;
}
/**
* In case the response can not be hidden, whether to fallback on non-hidden question or not.
*
* @return bool
*/
public function isHiddenFallback()
{
return $this->hiddenFallback;
}
/**
* Sets whether to fallback on non-hidden question if the response can not be hidden.
*
* @param bool $fallback
*
* @return $this
*/
public function setHiddenFallback($fallback)
{
$this->hiddenFallback = (bool) $fallback;
return $this;
}
/**
* Gets values for the autocompleter.
*
* @return iterable|null
*/
public function getAutocompleterValues()
{
$callback = $this->getAutocompleterCallback();
return $callback ? $callback('') : null;
}
/**
* Sets values for the autocompleter.
*
* @return $this
*
* @throws LogicException
*/
public function setAutocompleterValues(?iterable $values)
{
if (\is_array($values)) {
$values = $this->isAssoc($values) ? array_merge(array_keys($values), array_values($values)) : array_values($values);
$callback = static function () use ($values) {
return $values;
};
} elseif ($values instanceof \Traversable) {
$valueCache = null;
$callback = static function () use ($values, &$valueCache) {
return $valueCache ?? $valueCache = iterator_to_array($values, false);
};
} else {
$callback = null;
}
return $this->setAutocompleterCallback($callback);
}
/**
* Gets the callback function used for the autocompleter.
*/
public function getAutocompleterCallback(): ?callable
{
return $this->autocompleterCallback;
}
/**
* Sets the callback function used for the autocompleter.
*
* The callback is passed the user input as argument and should return an iterable of corresponding suggestions.
*
* @return $this
*/
public function setAutocompleterCallback(callable $callback = null): self
{
if ($this->hidden && null !== $callback) {
throw new LogicException('A hidden question cannot use the autocompleter.');
}
$this->autocompleterCallback = $callback;
return $this;
}
/**
* Sets a validator for the question.
*
* @return $this
*/
public function setValidator(callable $validator = null)
{
$this->validator = $validator;
return $this;
}
/**
* Gets the validator for the question.
*
* @return callable|null
*/
public function getValidator()
{
return $this->validator;
}
/**
* Sets the maximum number of attempts.
*
* Null means an unlimited number of attempts.
*
* @return $this
*
* @throws InvalidArgumentException in case the number of attempts is invalid
*/
public function setMaxAttempts(?int $attempts)
{
if (null !== $attempts) {
$attempts = (int) $attempts;
if ($attempts < 1) {
throw new InvalidArgumentException('Maximum number of attempts must be a positive value.');
}
}
$this->attempts = $attempts;
return $this;
}
/**
* Gets the maximum number of attempts.
*
* Null means an unlimited number of attempts.
*
* @return int|null
*/
public function getMaxAttempts()
{
return $this->attempts;
}
/**
* Sets a normalizer for the response.
*
* The normalizer can be a callable (a string), a closure or a class implementing __invoke.
*
* @return $this
*/
public function setNormalizer(callable $normalizer)
{
$this->normalizer = $normalizer;
return $this;
}
/**
* Gets the normalizer for the response.
*
* The normalizer can ba a callable (a string), a closure or a class implementing __invoke.
*
* @return callable|null
*/
public function getNormalizer()
{
return $this->normalizer;
}
protected function isAssoc(array $array)
{
return (bool) \count(array_filter(array_keys($array), 'is_string'));
}
public function isTrimmable(): bool
{
return $this->trimmable;
}
/**
* @return $this
*/
public function setTrimmable(bool $trimmable): self
{
$this->trimmable = $trimmable;
return $this;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Logger;
use Psr\Log\AbstractLogger;
use Psr\Log\InvalidArgumentException;
use Psr\Log\LogLevel;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* PSR-3 compliant console logger.
*
* @author Kévin Dunglas <dunglas@gmail.com>
*
* @see https://www.php-fig.org/psr/psr-3/
*/
class ConsoleLogger extends AbstractLogger
{
const INFO = 'info';
const ERROR = 'error';
private $output;
private $verbosityLevelMap = [
LogLevel::EMERGENCY => OutputInterface::VERBOSITY_NORMAL,
LogLevel::ALERT => OutputInterface::VERBOSITY_NORMAL,
LogLevel::CRITICAL => OutputInterface::VERBOSITY_NORMAL,
LogLevel::ERROR => OutputInterface::VERBOSITY_NORMAL,
LogLevel::WARNING => OutputInterface::VERBOSITY_NORMAL,
LogLevel::NOTICE => OutputInterface::VERBOSITY_VERBOSE,
LogLevel::INFO => OutputInterface::VERBOSITY_VERY_VERBOSE,
LogLevel::DEBUG => OutputInterface::VERBOSITY_DEBUG,
];
private $formatLevelMap = [
LogLevel::EMERGENCY => self::ERROR,
LogLevel::ALERT => self::ERROR,
LogLevel::CRITICAL => self::ERROR,
LogLevel::ERROR => self::ERROR,
LogLevel::WARNING => self::INFO,
LogLevel::NOTICE => self::INFO,
LogLevel::INFO => self::INFO,
LogLevel::DEBUG => self::INFO,
];
private $errored = false;
public function __construct(OutputInterface $output, array $verbosityLevelMap = [], array $formatLevelMap = [])
{
$this->output = $output;
$this->verbosityLevelMap = $verbosityLevelMap + $this->verbosityLevelMap;
$this->formatLevelMap = $formatLevelMap + $this->formatLevelMap;
}
/**
* {@inheritdoc}
*
* @return void
*/
public function log($level, $message, array $context = [])
{
if (!isset($this->verbosityLevelMap[$level])) {
throw new InvalidArgumentException(sprintf('The log level "%s" does not exist.', $level));
}
$output = $this->output;
// Write to the error output if necessary and available
if (self::ERROR === $this->formatLevelMap[$level]) {
if ($this->output instanceof ConsoleOutputInterface) {
$output = $output->getErrorOutput();
}
$this->errored = true;
}
// the if condition check isn't necessary -- it's the same one that $output will do internally anyway.
// We only do it for efficiency here as the message formatting is relatively expensive.
if ($output->getVerbosity() >= $this->verbosityLevelMap[$level]) {
$output->writeln(sprintf('<%1$s>[%2$s] %3$s</%1$s>', $this->formatLevelMap[$level], $level, $this->interpolate($message, $context)), $this->verbosityLevelMap[$level]);
}
}
/**
* Returns true when any messages have been logged at error levels.
*
* @return bool
*/
public function hasErrored()
{
return $this->errored;
}
/**
* Interpolates context values into the message placeholders.
*
* @author PHP Framework Interoperability Group
*/
private function interpolate(string $message, array $context): string
{
if (false === strpos($message, '{')) {
return $message;
}
$replacements = [];
foreach ($context as $key => $val) {
if (null === $val || is_scalar($val) || (\is_object($val) && method_exists($val, '__toString'))) {
$replacements["{{$key}}"] = $val;
} elseif ($val instanceof \DateTimeInterface) {
$replacements["{{$key}}"] = $val->format(\DateTime::RFC3339);
} elseif (\is_object($val)) {
$replacements["{{$key}}"] = '[object '.\get_class($val).']';
} else {
$replacements["{{$key}}"] = '['.\gettype($val).']';
}
}
return strtr($message, $replacements);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Command\HelpCommand;
use Symfony\Component\Console\Command\ListCommand;
use Symfony\Component\Console\Command\SignalableCommandInterface;
use Symfony\Component\Console\CommandLoader\CommandLoaderInterface;
use Symfony\Component\Console\Event\ConsoleCommandEvent;
use Symfony\Component\Console\Event\ConsoleErrorEvent;
use Symfony\Component\Console\Event\ConsoleSignalEvent;
use Symfony\Component\Console\Event\ConsoleTerminateEvent;
use Symfony\Component\Console\Exception\CommandNotFoundException;
use Symfony\Component\Console\Exception\ExceptionInterface;
use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\Console\Exception\NamespaceNotFoundException;
use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Helper\DebugFormatterHelper;
use Symfony\Component\Console\Helper\FormatterHelper;
use Symfony\Component\Console\Helper\Helper;
use Symfony\Component\Console\Helper\HelperSet;
use Symfony\Component\Console\Helper\ProcessHelper;
use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputAwareInterface;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\SignalRegistry\SignalRegistry;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\ErrorHandler\ErrorHandler;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
use Symfony\Contracts\Service\ResetInterface;
/**
* An Application is the container for a collection of commands.
*
* It is the main entry point of a Console application.
*
* This class is optimized for a standard CLI environment.
*
* Usage:
*
* $app = new Application('myapp', '1.0 (stable)');
* $app->add(new SimpleCommand());
* $app->run();
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class Application implements ResetInterface
{
private $commands = [];
private $wantHelps = false;
private $runningCommand;
private $name;
private $version;
private $commandLoader;
private $catchExceptions = true;
private $autoExit = true;
private $definition;
private $helperSet;
private $dispatcher;
private $terminal;
private $defaultCommand;
private $singleCommand = false;
private $initialized;
private $signalRegistry;
private $signalsToDispatchEvent = [];
public function __construct(string $name = 'UNKNOWN', string $version = 'UNKNOWN')
{
$this->name = $name;
$this->version = $version;
$this->terminal = new Terminal();
$this->defaultCommand = 'list';
if (\defined('SIGINT') && SignalRegistry::isSupported()) {
$this->signalRegistry = new SignalRegistry();
$this->signalsToDispatchEvent = [\SIGINT, \SIGTERM, \SIGUSR1, \SIGUSR2];
}
}
/**
* @final
*/
public function setDispatcher(EventDispatcherInterface $dispatcher)
{
$this->dispatcher = $dispatcher;
}
public function setCommandLoader(CommandLoaderInterface $commandLoader)
{
$this->commandLoader = $commandLoader;
}
public function getSignalRegistry(): SignalRegistry
{
if (!$this->signalRegistry) {
throw new RuntimeException('Signals are not supported. Make sure that the `pcntl` extension is installed and that "pcntl_*" functions are not disabled by your php.ini\'s "disable_functions" directive.');
}
return $this->signalRegistry;
}
public function setSignalsToDispatchEvent(int ...$signalsToDispatchEvent)
{
$this->signalsToDispatchEvent = $signalsToDispatchEvent;
}
/**
* Runs the current application.
*
* @return int 0 if everything went fine, or an error code
*
* @throws \Exception When running fails. Bypass this when {@link setCatchExceptions()}.
*/
public function run(InputInterface $input = null, OutputInterface $output = null)
{
if (\function_exists('putenv')) {
@putenv('LINES='.$this->terminal->getHeight());
@putenv('COLUMNS='.$this->terminal->getWidth());
}
if (null === $input) {
$input = new ArgvInput();
}
if (null === $output) {
$output = new ConsoleOutput();
}
$renderException = function (\Throwable $e) use ($output) {
if ($output instanceof ConsoleOutputInterface) {
$this->renderThrowable($e, $output->getErrorOutput());
} else {
$this->renderThrowable($e, $output);
}
};
if ($phpHandler = set_exception_handler($renderException)) {
restore_exception_handler();
if (!\is_array($phpHandler) || !$phpHandler[0] instanceof ErrorHandler) {
$errorHandler = true;
} elseif ($errorHandler = $phpHandler[0]->setExceptionHandler($renderException)) {
$phpHandler[0]->setExceptionHandler($errorHandler);
}
}
$this->configureIO($input, $output);
try {
$exitCode = $this->doRun($input, $output);
} catch (\Exception $e) {
if (!$this->catchExceptions) {
throw $e;
}
$renderException($e);
$exitCode = $e->getCode();
if (is_numeric($exitCode)) {
$exitCode = (int) $exitCode;
if (0 === $exitCode) {
$exitCode = 1;
}
} else {
$exitCode = 1;
}
} finally {
// if the exception handler changed, keep it
// otherwise, unregister $renderException
if (!$phpHandler) {
if (set_exception_handler($renderException) === $renderException) {
restore_exception_handler();
}
restore_exception_handler();
} elseif (!$errorHandler) {
$finalHandler = $phpHandler[0]->setExceptionHandler(null);
if ($finalHandler !== $renderException) {
$phpHandler[0]->setExceptionHandler($finalHandler);
}
}
}
if ($this->autoExit) {
if ($exitCode > 255) {
$exitCode = 255;
}
exit($exitCode);
}
return $exitCode;
}
/**
* Runs the current application.
*
* @return int 0 if everything went fine, or an error code
*/
public function doRun(InputInterface $input, OutputInterface $output)
{
if (true === $input->hasParameterOption(['--version', '-V'], true)) {
$output->writeln($this->getLongVersion());
return 0;
}
try {
// Makes ArgvInput::getFirstArgument() able to distinguish an option from an argument.
$input->bind($this->getDefinition());
} catch (ExceptionInterface $e) {
// Errors must be ignored, full binding/validation happens later when the command is known.
}
$name = $this->getCommandName($input);
if (true === $input->hasParameterOption(['--help', '-h'], true)) {
if (!$name) {
$name = 'help';
$input = new ArrayInput(['command_name' => $this->defaultCommand]);
} else {
$this->wantHelps = true;
}
}
if (!$name) {
$name = $this->defaultCommand;
$definition = $this->getDefinition();
$definition->setArguments(array_merge(
$definition->getArguments(),
[
'command' => new InputArgument('command', InputArgument::OPTIONAL, $definition->getArgument('command')->getDescription(), $name),
]
));
}
try {
$this->runningCommand = null;
// the command name MUST be the first element of the input
$command = $this->find($name);
} catch (\Throwable $e) {
if (!($e instanceof CommandNotFoundException && !$e instanceof NamespaceNotFoundException) || 1 !== \count($alternatives = $e->getAlternatives()) || !$input->isInteractive()) {
if (null !== $this->dispatcher) {
$event = new ConsoleErrorEvent($input, $output, $e);
$this->dispatcher->dispatch($event, ConsoleEvents::ERROR);
if (0 === $event->getExitCode()) {
return 0;
}
$e = $event->getError();
}
throw $e;
}
$alternative = $alternatives[0];
$style = new SymfonyStyle($input, $output);
$style->block(sprintf("\nCommand \"%s\" is not defined.\n", $name), null, 'error');
if (!$style->confirm(sprintf('Do you want to run "%s" instead? ', $alternative), false)) {
if (null !== $this->dispatcher) {
$event = new ConsoleErrorEvent($input, $output, $e);
$this->dispatcher->dispatch($event, ConsoleEvents::ERROR);
return $event->getExitCode();
}
return 1;
}
$command = $this->find($alternative);
}
$this->runningCommand = $command;
$exitCode = $this->doRunCommand($command, $input, $output);
$this->runningCommand = null;
return $exitCode;
}
/**
* {@inheritdoc}
*/
public function reset()
{
}
public function setHelperSet(HelperSet $helperSet)
{
$this->helperSet = $helperSet;
}
/**
* Get the helper set associated with the command.
*
* @return HelperSet The HelperSet instance associated with this command
*/
public function getHelperSet()
{
if (!$this->helperSet) {
$this->helperSet = $this->getDefaultHelperSet();
}
return $this->helperSet;
}
public function setDefinition(InputDefinition $definition)
{
$this->definition = $definition;
}
/**
* Gets the InputDefinition related to this Application.
*
* @return InputDefinition The InputDefinition instance
*/
public function getDefinition()
{
if (!$this->definition) {
$this->definition = $this->getDefaultInputDefinition();
}
if ($this->singleCommand) {
$inputDefinition = $this->definition;
$inputDefinition->setArguments();
return $inputDefinition;
}
return $this->definition;
}
/**
* Gets the help message.
*
* @return string A help message
*/
public function getHelp()
{
return $this->getLongVersion();
}
/**
* Gets whether to catch exceptions or not during commands execution.
*
* @return bool Whether to catch exceptions or not during commands execution
*/
public function areExceptionsCaught()
{
return $this->catchExceptions;
}
/**
* Sets whether to catch exceptions or not during commands execution.
*/
public function setCatchExceptions(bool $boolean)
{
$this->catchExceptions = $boolean;
}
/**
* Gets whether to automatically exit after a command execution or not.
*
* @return bool Whether to automatically exit after a command execution or not
*/
public function isAutoExitEnabled()
{
return $this->autoExit;
}
/**
* Sets whether to automatically exit after a command execution or not.
*/
public function setAutoExit(bool $boolean)
{
$this->autoExit = $boolean;
}
/**
* Gets the name of the application.
*
* @return string The application name
*/
public function getName()
{
return $this->name;
}
/**
* Sets the application name.
**/
public function setName(string $name)
{
$this->name = $name;
}
/**
* Gets the application version.
*
* @return string The application version
*/
public function getVersion()
{
return $this->version;
}
/**
* Sets the application version.
*/
public function setVersion(string $version)
{
$this->version = $version;
}
/**
* Returns the long version of the application.
*
* @return string The long application version
*/
public function getLongVersion()
{
if ('UNKNOWN' !== $this->getName()) {
if ('UNKNOWN' !== $this->getVersion()) {
return sprintf('%s <info>%s</info>', $this->getName(), $this->getVersion());
}
return $this->getName();
}
return 'Console Tool';
}
/**
* Registers a new command.
*
* @return Command The newly created command
*/
public function register(string $name)
{
return $this->add(new Command($name));
}
/**
* Adds an array of command objects.
*
* If a Command is not enabled it will not be added.
*
* @param Command[] $commands An array of commands
*/
public function addCommands(array $commands)
{
foreach ($commands as $command) {
$this->add($command);
}
}
/**
* Adds a command object.
*
* If a command with the same name already exists, it will be overridden.
* If the command is not enabled it will not be added.
*
* @return Command|null The registered command if enabled or null
*/
public function add(Command $command)
{
$this->init();
$command->setApplication($this);
if (!$command->isEnabled()) {
$command->setApplication(null);
return null;
}
// Will throw if the command is not correctly initialized.
$command->getDefinition();
if (!$command->getName()) {
throw new LogicException(sprintf('The command defined in "%s" cannot have an empty name.', get_debug_type($command)));
}
$this->commands[$command->getName()] = $command;
foreach ($command->getAliases() as $alias) {
$this->commands[$alias] = $command;
}
return $command;
}
/**
* Returns a registered command by name or alias.
*
* @return Command A Command object
*
* @throws CommandNotFoundException When given command name does not exist
*/
public function get(string $name)
{
$this->init();
if (!$this->has($name)) {
throw new CommandNotFoundException(sprintf('The command "%s" does not exist.', $name));
}
// When the command has a different name than the one used at the command loader level
if (!isset($this->commands[$name])) {
throw new CommandNotFoundException(sprintf('The "%s" command cannot be found because it is registered under multiple names. Make sure you don\'t set a different name via constructor or "setName()".', $name));
}
$command = $this->commands[$name];
if ($this->wantHelps) {
$this->wantHelps = false;
$helpCommand = $this->get('help');
$helpCommand->setCommand($command);
return $helpCommand;
}
return $command;
}
/**
* Returns true if the command exists, false otherwise.
*
* @return bool true if the command exists, false otherwise
*/
public function has(string $name)
{
$this->init();
return isset($this->commands[$name]) || ($this->commandLoader && $this->commandLoader->has($name) && $this->add($this->commandLoader->get($name)));
}
/**
* Returns an array of all unique namespaces used by currently registered commands.
*
* It does not return the global namespace which always exists.
*
* @return string[] An array of namespaces
*/
public function getNamespaces()
{
$namespaces = [];
foreach ($this->all() as $command) {
if ($command->isHidden()) {
continue;
}
$namespaces = array_merge($namespaces, $this->extractAllNamespaces($command->getName()));
foreach ($command->getAliases() as $alias) {
$namespaces = array_merge($namespaces, $this->extractAllNamespaces($alias));
}
}
return array_values(array_unique(array_filter($namespaces)));
}
/**
* Finds a registered namespace by a name or an abbreviation.
*
* @return string A registered namespace
*
* @throws NamespaceNotFoundException When namespace is incorrect or ambiguous
*/
public function findNamespace(string $namespace)
{
$allNamespaces = $this->getNamespaces();
$expr = preg_replace_callback('{([^:]+|)}', function ($matches) { return preg_quote($matches[1]).'[^:]*'; }, $namespace);
$namespaces = preg_grep('{^'.$expr.'}', $allNamespaces);
if (empty($namespaces)) {
$message = sprintf('There are no commands defined in the "%s" namespace.', $namespace);
if ($alternatives = $this->findAlternatives($namespace, $allNamespaces)) {
if (1 == \count($alternatives)) {
$message .= "\n\nDid you mean this?\n ";
} else {
$message .= "\n\nDid you mean one of these?\n ";
}
$message .= implode("\n ", $alternatives);
}
throw new NamespaceNotFoundException($message, $alternatives);
}
$exact = \in_array($namespace, $namespaces, true);
if (\count($namespaces) > 1 && !$exact) {
throw new NamespaceNotFoundException(sprintf("The namespace \"%s\" is ambiguous.\nDid you mean one of these?\n%s.", $namespace, $this->getAbbreviationSuggestions(array_values($namespaces))), array_values($namespaces));
}
return $exact ? $namespace : reset($namespaces);
}
/**
* Finds a command by name or alias.
*
* Contrary to get, this command tries to find the best
* match if you give it an abbreviation of a name or alias.
*
* @return Command A Command instance
*
* @throws CommandNotFoundException When command name is incorrect or ambiguous
*/
public function find(string $name)
{
$this->init();
$aliases = [];
foreach ($this->commands as $command) {
foreach ($command->getAliases() as $alias) {
if (!$this->has($alias)) {
$this->commands[$alias] = $command;
}
}
}
if ($this->has($name)) {
return $this->get($name);
}
$allCommands = $this->commandLoader ? array_merge($this->commandLoader->getNames(), array_keys($this->commands)) : array_keys($this->commands);
$expr = preg_replace_callback('{([^:]+|)}', function ($matches) { return preg_quote($matches[1]).'[^:]*'; }, $name);
$commands = preg_grep('{^'.$expr.'}', $allCommands);
if (empty($commands)) {
$commands = preg_grep('{^'.$expr.'}i', $allCommands);
}
// if no commands matched or we just matched namespaces
if (empty($commands) || \count(preg_grep('{^'.$expr.'$}i', $commands)) < 1) {
if (false !== $pos = strrpos($name, ':')) {
// check if a namespace exists and contains commands
$this->findNamespace(substr($name, 0, $pos));
}
$message = sprintf('Command "%s" is not defined.', $name);
if ($alternatives = $this->findAlternatives($name, $allCommands)) {
// remove hidden commands
$alternatives = array_filter($alternatives, function ($name) {
return !$this->get($name)->isHidden();
});
if (1 == \count($alternatives)) {
$message .= "\n\nDid you mean this?\n ";
} else {
$message .= "\n\nDid you mean one of these?\n ";
}
$message .= implode("\n ", $alternatives);
}
throw new CommandNotFoundException($message, array_values($alternatives));
}
// filter out aliases for commands which are already on the list
if (\count($commands) > 1) {
$commandList = $this->commandLoader ? array_merge(array_flip($this->commandLoader->getNames()), $this->commands) : $this->commands;
$commands = array_unique(array_filter($commands, function ($nameOrAlias) use (&$commandList, $commands, &$aliases) {
if (!$commandList[$nameOrAlias] instanceof Command) {
$commandList[$nameOrAlias] = $this->commandLoader->get($nameOrAlias);
}
$commandName = $commandList[$nameOrAlias]->getName();
$aliases[$nameOrAlias] = $commandName;
return $commandName === $nameOrAlias || !\in_array($commandName, $commands);
}));
}
if (\count($commands) > 1) {
$usableWidth = $this->terminal->getWidth() - 10;
$abbrevs = array_values($commands);
$maxLen = 0;
foreach ($abbrevs as $abbrev) {
$maxLen = max(Helper::strlen($abbrev), $maxLen);
}
$abbrevs = array_map(function ($cmd) use ($commandList, $usableWidth, $maxLen, &$commands) {
if ($commandList[$cmd]->isHidden()) {
unset($commands[array_search($cmd, $commands)]);
return false;
}
$abbrev = str_pad($cmd, $maxLen, ' ').' '.$commandList[$cmd]->getDescription();
return Helper::strlen($abbrev) > $usableWidth ? Helper::substr($abbrev, 0, $usableWidth - 3).'...' : $abbrev;
}, array_values($commands));
if (\count($commands) > 1) {
$suggestions = $this->getAbbreviationSuggestions(array_filter($abbrevs));
throw new CommandNotFoundException(sprintf("Command \"%s\" is ambiguous.\nDid you mean one of these?\n%s.", $name, $suggestions), array_values($commands));
}
}
$command = $this->get(reset($commands));
if ($command->isHidden()) {
throw new CommandNotFoundException(sprintf('The command "%s" does not exist.', $name));
}
return $command;
}
/**
* Gets the commands (registered in the given namespace if provided).
*
* The array keys are the full names and the values the command instances.
*
* @return Command[] An array of Command instances
*/
public function all(string $namespace = null)
{
$this->init();
if (null === $namespace) {
if (!$this->commandLoader) {
return $this->commands;
}
$commands = $this->commands;
foreach ($this->commandLoader->getNames() as $name) {
if (!isset($commands[$name]) && $this->has($name)) {
$commands[$name] = $this->get($name);
}
}
return $commands;
}
$commands = [];
foreach ($this->commands as $name => $command) {
if ($namespace === $this->extractNamespace($name, substr_count($namespace, ':') + 1)) {
$commands[$name] = $command;
}
}
if ($this->commandLoader) {
foreach ($this->commandLoader->getNames() as $name) {
if (!isset($commands[$name]) && $namespace === $this->extractNamespace($name, substr_count($namespace, ':') + 1) && $this->has($name)) {
$commands[$name] = $this->get($name);
}
}
}
return $commands;
}
/**
* Returns an array of possible abbreviations given a set of names.
*
* @return string[][] An array of abbreviations
*/
public static function getAbbreviations(array $names)
{
$abbrevs = [];
foreach ($names as $name) {
for ($len = \strlen($name); $len > 0; --$len) {
$abbrev = substr($name, 0, $len);
$abbrevs[$abbrev][] = $name;
}
}
return $abbrevs;
}
public function renderThrowable(\Throwable $e, OutputInterface $output): void
{
$output->writeln('', OutputInterface::VERBOSITY_QUIET);
$this->doRenderThrowable($e, $output);
if (null !== $this->runningCommand) {
$output->writeln(sprintf('<info>%s</info>', sprintf($this->runningCommand->getSynopsis(), $this->getName())), OutputInterface::VERBOSITY_QUIET);
$output->writeln('', OutputInterface::VERBOSITY_QUIET);
}
}
protected function doRenderThrowable(\Throwable $e, OutputInterface $output): void
{
do {
$message = trim($e->getMessage());
if ('' === $message || OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) {
$class = get_debug_type($e);
$title = sprintf(' [%s%s] ', $class, 0 !== ($code = $e->getCode()) ? ' ('.$code.')' : '');
$len = Helper::strlen($title);
} else {
$len = 0;
}
if (false !== strpos($message, "@anonymous\0")) {
$message = preg_replace_callback('/[a-zA-Z_\x7f-\xff][\\\\a-zA-Z0-9_\x7f-\xff]*+@anonymous\x00.*?\.php(?:0x?|:[0-9]++\$)[0-9a-fA-F]++/', function ($m) {
return class_exists($m[0], false) ? (get_parent_class($m[0]) ?: key(class_implements($m[0])) ?: 'class').'@anonymous' : $m[0];
}, $message);
}
$width = $this->terminal->getWidth() ? $this->terminal->getWidth() - 1 : \PHP_INT_MAX;
$lines = [];
foreach ('' !== $message ? preg_split('/\r?\n/', $message) : [] as $line) {
foreach ($this->splitStringByWidth($line, $width - 4) as $line) {
// pre-format lines to get the right string length
$lineLength = Helper::strlen($line) + 4;
$lines[] = [$line, $lineLength];
$len = max($lineLength, $len);
}
}
$messages = [];
if (!$e instanceof ExceptionInterface || OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) {
$messages[] = sprintf('<comment>%s</comment>', OutputFormatter::escape(sprintf('In %s line %s:', basename($e->getFile()) ?: 'n/a', $e->getLine() ?: 'n/a')));
}
$messages[] = $emptyLine = sprintf('<error>%s</error>', str_repeat(' ', $len));
if ('' === $message || OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) {
$messages[] = sprintf('<error>%s%s</error>', $title, str_repeat(' ', max(0, $len - Helper::strlen($title))));
}
foreach ($lines as $line) {
$messages[] = sprintf('<error> %s %s</error>', OutputFormatter::escape($line[0]), str_repeat(' ', $len - $line[1]));
}
$messages[] = $emptyLine;
$messages[] = '';
$output->writeln($messages, OutputInterface::VERBOSITY_QUIET);
if (OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) {
$output->writeln('<comment>Exception trace:</comment>', OutputInterface::VERBOSITY_QUIET);
// exception related properties
$trace = $e->getTrace();
array_unshift($trace, [
'function' => '',
'file' => $e->getFile() ?: 'n/a',
'line' => $e->getLine() ?: 'n/a',
'args' => [],
]);
for ($i = 0, $count = \count($trace); $i < $count; ++$i) {
$class = isset($trace[$i]['class']) ? $trace[$i]['class'] : '';
$type = isset($trace[$i]['type']) ? $trace[$i]['type'] : '';
$function = isset($trace[$i]['function']) ? $trace[$i]['function'] : '';
$file = isset($trace[$i]['file']) ? $trace[$i]['file'] : 'n/a';
$line = isset($trace[$i]['line']) ? $trace[$i]['line'] : 'n/a';
$output->writeln(sprintf(' %s%s at <info>%s:%s</info>', $class, $function ? $type.$function.'()' : '', $file, $line), OutputInterface::VERBOSITY_QUIET);
}
$output->writeln('', OutputInterface::VERBOSITY_QUIET);
}
} while ($e = $e->getPrevious());
}
/**
* Configures the input and output instances based on the user arguments and options.
*/
protected function configureIO(InputInterface $input, OutputInterface $output)
{
if (true === $input->hasParameterOption(['--ansi'], true)) {
$output->setDecorated(true);
} elseif (true === $input->hasParameterOption(['--no-ansi'], true)) {
$output->setDecorated(false);
}
if (true === $input->hasParameterOption(['--no-interaction', '-n'], true)) {
$input->setInteractive(false);
}
switch ($shellVerbosity = (int) getenv('SHELL_VERBOSITY')) {
case -1: $output->setVerbosity(OutputInterface::VERBOSITY_QUIET); break;
case 1: $output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE); break;
case 2: $output->setVerbosity(OutputInterface::VERBOSITY_VERY_VERBOSE); break;
case 3: $output->setVerbosity(OutputInterface::VERBOSITY_DEBUG); break;
default: $shellVerbosity = 0; break;
}
if (true === $input->hasParameterOption(['--quiet', '-q'], true)) {
$output->setVerbosity(OutputInterface::VERBOSITY_QUIET);
$shellVerbosity = -1;
} else {
if ($input->hasParameterOption('-vvv', true) || $input->hasParameterOption('--verbose=3', true) || 3 === $input->getParameterOption('--verbose', false, true)) {
$output->setVerbosity(OutputInterface::VERBOSITY_DEBUG);
$shellVerbosity = 3;
} elseif ($input->hasParameterOption('-vv', true) || $input->hasParameterOption('--verbose=2', true) || 2 === $input->getParameterOption('--verbose', false, true)) {
$output->setVerbosity(OutputInterface::VERBOSITY_VERY_VERBOSE);
$shellVerbosity = 2;
} elseif ($input->hasParameterOption('-v', true) || $input->hasParameterOption('--verbose=1', true) || $input->hasParameterOption('--verbose', true) || $input->getParameterOption('--verbose', false, true)) {
$output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE);
$shellVerbosity = 1;
}
}
if (-1 === $shellVerbosity) {
$input->setInteractive(false);
}
if (\function_exists('putenv')) {
@putenv('SHELL_VERBOSITY='.$shellVerbosity);
}
$_ENV['SHELL_VERBOSITY'] = $shellVerbosity;
$_SERVER['SHELL_VERBOSITY'] = $shellVerbosity;
}
/**
* Runs the current command.
*
* If an event dispatcher has been attached to the application,
* events are also dispatched during the life-cycle of the command.
*
* @return int 0 if everything went fine, or an error code
*/
protected function doRunCommand(Command $command, InputInterface $input, OutputInterface $output)
{
foreach ($command->getHelperSet() as $helper) {
if ($helper instanceof InputAwareInterface) {
$helper->setInput($input);
}
}
if ($command instanceof SignalableCommandInterface) {
if (!$this->signalRegistry) {
throw new RuntimeException('Unable to subscribe to signal events. Make sure that the `pcntl` extension is installed and that "pcntl_*" functions are not disabled by your php.ini\'s "disable_functions" directive.');
}
if ($this->dispatcher) {
foreach ($this->signalsToDispatchEvent as $signal) {
$event = new ConsoleSignalEvent($command, $input, $output, $signal);
$this->signalRegistry->register($signal, function ($signal, $hasNext) use ($event) {
$this->dispatcher->dispatch($event, ConsoleEvents::SIGNAL);
// No more handlers, we try to simulate PHP default behavior
if (!$hasNext) {
if (!\in_array($signal, [\SIGUSR1, \SIGUSR2], true)) {
exit(0);
}
}
});
}
}
foreach ($command->getSubscribedSignals() as $signal) {
$this->signalRegistry->register($signal, [$command, 'handleSignal']);
}
}
if (null === $this->dispatcher) {
return $command->run($input, $output);
}
// bind before the console.command event, so the listeners have access to input options/arguments
try {
$command->mergeApplicationDefinition();
$input->bind($command->getDefinition());
} catch (ExceptionInterface $e) {
// ignore invalid options/arguments for now, to allow the event listeners to customize the InputDefinition
}
$event = new ConsoleCommandEvent($command, $input, $output);
$e = null;
try {
$this->dispatcher->dispatch($event, ConsoleEvents::COMMAND);
if ($event->commandShouldRun()) {
$exitCode = $command->run($input, $output);
} else {
$exitCode = ConsoleCommandEvent::RETURN_CODE_DISABLED;
}
} catch (\Throwable $e) {
$event = new ConsoleErrorEvent($input, $output, $e, $command);
$this->dispatcher->dispatch($event, ConsoleEvents::ERROR);
$e = $event->getError();
if (0 === $exitCode = $event->getExitCode()) {
$e = null;
}
}
$event = new ConsoleTerminateEvent($command, $input, $output, $exitCode);
$this->dispatcher->dispatch($event, ConsoleEvents::TERMINATE);
if (null !== $e) {
throw $e;
}
return $event->getExitCode();
}
/**
* Gets the name of the command based on input.
*
* @return string|null
*/
protected function getCommandName(InputInterface $input)
{
return $this->singleCommand ? $this->defaultCommand : $input->getFirstArgument();
}
/**
* Gets the default input definition.
*
* @return InputDefinition An InputDefinition instance
*/
protected function getDefaultInputDefinition()
{
return new InputDefinition([
new InputArgument('command', InputArgument::REQUIRED, 'The command to execute'),
new InputOption('--help', '-h', InputOption::VALUE_NONE, 'Display help for the given command. When no command is given display help for the <info>'.$this->defaultCommand.'</info> command'),
new InputOption('--quiet', '-q', InputOption::VALUE_NONE, 'Do not output any message'),
new InputOption('--verbose', '-v|vv|vvv', InputOption::VALUE_NONE, 'Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug'),
new InputOption('--version', '-V', InputOption::VALUE_NONE, 'Display this application version'),
new InputOption('--ansi', '', InputOption::VALUE_NONE, 'Force ANSI output'),
new InputOption('--no-ansi', '', InputOption::VALUE_NONE, 'Disable ANSI output'),
new InputOption('--no-interaction', '-n', InputOption::VALUE_NONE, 'Do not ask any interactive question'),
]);
}
/**
* Gets the default commands that should always be available.
*
* @return Command[] An array of default Command instances
*/
protected function getDefaultCommands()
{
return [new HelpCommand(), new ListCommand()];
}
/**
* Gets the default helper set with the helpers that should always be available.
*
* @return HelperSet A HelperSet instance
*/
protected function getDefaultHelperSet()
{
return new HelperSet([
new FormatterHelper(),
new DebugFormatterHelper(),
new ProcessHelper(),
new QuestionHelper(),
]);
}
/**
* Returns abbreviated suggestions in string format.
*/
private function getAbbreviationSuggestions(array $abbrevs): string
{
return ' '.implode("\n ", $abbrevs);
}
/**
* Returns the namespace part of the command name.
*
* This method is not part of public API and should not be used directly.
*
* @return string The namespace of the command
*/
public function extractNamespace(string $name, int $limit = null)
{
$parts = explode(':', $name, -1);
return implode(':', null === $limit ? $parts : \array_slice($parts, 0, $limit));
}
/**
* Finds alternative of $name among $collection,
* if nothing is found in $collection, try in $abbrevs.
*
* @return string[] A sorted array of similar string
*/
private function findAlternatives(string $name, iterable $collection): array
{
$threshold = 1e3;
$alternatives = [];
$collectionParts = [];
foreach ($collection as $item) {
$collectionParts[$item] = explode(':', $item);
}
foreach (explode(':', $name) as $i => $subname) {
foreach ($collectionParts as $collectionName => $parts) {
$exists = isset($alternatives[$collectionName]);
if (!isset($parts[$i]) && $exists) {
$alternatives[$collectionName] += $threshold;
continue;
} elseif (!isset($parts[$i])) {
continue;
}
$lev = levenshtein($subname, $parts[$i]);
if ($lev <= \strlen($subname) / 3 || '' !== $subname && false !== strpos($parts[$i], $subname)) {
$alternatives[$collectionName] = $exists ? $alternatives[$collectionName] + $lev : $lev;
} elseif ($exists) {
$alternatives[$collectionName] += $threshold;
}
}
}
foreach ($collection as $item) {
$lev = levenshtein($name, $item);
if ($lev <= \strlen($name) / 3 || false !== strpos($item, $name)) {
$alternatives[$item] = isset($alternatives[$item]) ? $alternatives[$item] - $lev : $lev;
}
}
$alternatives = array_filter($alternatives, function ($lev) use ($threshold) { return $lev < 2 * $threshold; });
ksort($alternatives, \SORT_NATURAL | \SORT_FLAG_CASE);
return array_keys($alternatives);
}
/**
* Sets the default Command name.
*
* @return self
*/
public function setDefaultCommand(string $commandName, bool $isSingleCommand = false)
{
$this->defaultCommand = $commandName;
if ($isSingleCommand) {
// Ensure the command exist
$this->find($commandName);
$this->singleCommand = true;
}
return $this;
}
/**
* @internal
*/
public function isSingleCommand(): bool
{
return $this->singleCommand;
}
private function splitStringByWidth(string $string, int $width): array
{
// str_split is not suitable for multi-byte characters, we should use preg_split to get char array properly.
// additionally, array_slice() is not enough as some character has doubled width.
// we need a function to split string not by character count but by string width
if (false === $encoding = mb_detect_encoding($string, null, true)) {
return str_split($string, $width);
}
$utf8String = mb_convert_encoding($string, 'utf8', $encoding);
$lines = [];
$line = '';
$offset = 0;
while (preg_match('/.{1,10000}/u', $utf8String, $m, 0, $offset)) {
$offset += \strlen($m[0]);
foreach (preg_split('//u', $m[0]) as $char) {
// test if $char could be appended to current line
if (mb_strwidth($line.$char, 'utf8') <= $width) {
$line .= $char;
continue;
}
// if not, push current line to array and make new line
$lines[] = str_pad($line, $width);
$line = $char;
}
}
$lines[] = \count($lines) ? str_pad($line, $width) : $line;
mb_convert_variables($encoding, 'utf8', $lines);
return $lines;
}
/**
* Returns all namespaces of the command name.
*
* @return string[] The namespaces of the command
*/
private function extractAllNamespaces(string $name): array
{
// -1 as third argument is needed to skip the command short name when exploding
$parts = explode(':', $name, -1);
$namespaces = [];
foreach ($parts as $part) {
if (\count($namespaces)) {
$namespaces[] = end($namespaces).':'.$part;
} else {
$namespaces[] = $part;
}
}
return $namespaces;
}
private function init()
{
if ($this->initialized) {
return;
}
$this->initialized = true;
foreach ($this->getDefaultCommands() as $command) {
$this->add($command);
}
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Input;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\RuntimeException;
/**
* InputInterface is the interface implemented by all input classes.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
interface InputInterface
{
/**
* Returns the first argument from the raw parameters (not parsed).
*
* @return string|null The value of the first argument or null otherwise
*/
public function getFirstArgument();
/**
* Returns true if the raw parameters (not parsed) contain a value.
*
* This method is to be used to introspect the input parameters
* before they have been validated. It must be used carefully.
* Does not necessarily return the correct result for short options
* when multiple flags are combined in the same option.
*
* @param string|array $values The values to look for in the raw parameters (can be an array)
* @param bool $onlyParams Only check real parameters, skip those following an end of options (--) signal
*
* @return bool true if the value is contained in the raw parameters
*/
public function hasParameterOption($values, bool $onlyParams = false);
/**
* Returns the value of a raw option (not parsed).
*
* This method is to be used to introspect the input parameters
* before they have been validated. It must be used carefully.
* Does not necessarily return the correct result for short options
* when multiple flags are combined in the same option.
*
* @param string|array $values The value(s) to look for in the raw parameters (can be an array)
* @param mixed $default The default value to return if no result is found
* @param bool $onlyParams Only check real parameters, skip those following an end of options (--) signal
*
* @return mixed The option value
*/
public function getParameterOption($values, $default = false, bool $onlyParams = false);
/**
* Binds the current Input instance with the given arguments and options.
*
* @throws RuntimeException
*/
public function bind(InputDefinition $definition);
/**
* Validates the input.
*
* @throws RuntimeException When not enough arguments are given
*/
public function validate();
/**
* Returns all the given arguments merged with the default values.
*
* @return array
*/
public function getArguments();
/**
* Returns the argument value for a given argument name.
*
* @return string|string[]|null The argument value
*
* @throws InvalidArgumentException When argument given doesn't exist
*/
public function getArgument(string $name);
/**
* Sets an argument value by name.
*
* @param string|string[]|null $value The argument value
*
* @throws InvalidArgumentException When argument given doesn't exist
*/
public function setArgument(string $name, $value);
/**
* Returns true if an InputArgument object exists by name or position.
*
* @param string|int $name The InputArgument name or position
*
* @return bool true if the InputArgument object exists, false otherwise
*/
public function hasArgument($name);
/**
* Returns all the given options merged with the default values.
*
* @return array
*/
public function getOptions();
/**
* Returns the option value for a given option name.
*
* @return string|string[]|bool|null The option value
*
* @throws InvalidArgumentException When option given doesn't exist
*/
public function getOption(string $name);
/**
* Sets an option value by name.
*
* @param string|string[]|bool|null $value The option value
*
* @throws InvalidArgumentException When option given doesn't exist
*/
public function setOption(string $name, $value);
/**
* Returns true if an InputOption object exists by name.
*
* @return bool true if the InputOption object exists, false otherwise
*/
public function hasOption(string $name);
/**
* Is this input means interactive?
*
* @return bool
*/
public function isInteractive();
/**
* Sets the input interactivity.
*/
public function setInteractive(bool $interactive);
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Input;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\LogicException;
/**
* Represents a command line option.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class InputOption
{
const VALUE_NONE = 1;
const VALUE_REQUIRED = 2;
const VALUE_OPTIONAL = 4;
const VALUE_IS_ARRAY = 8;
private $name;
private $shortcut;
private $mode;
private $default;
private $description;
/**
* @param string $name The option name
* @param string|array|null $shortcut The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts
* @param int|null $mode The option mode: One of the VALUE_* constants
* @param string $description A description text
* @param string|string[]|int|bool|null $default The default value (must be null for self::VALUE_NONE)
*
* @throws InvalidArgumentException If option mode is invalid or incompatible
*/
public function __construct(string $name, $shortcut = null, int $mode = null, string $description = '', $default = null)
{
if (0 === strpos($name, '--')) {
$name = substr($name, 2);
}
if (empty($name)) {
throw new InvalidArgumentException('An option name cannot be empty.');
}
if (empty($shortcut)) {
$shortcut = null;
}
if (null !== $shortcut) {
if (\is_array($shortcut)) {
$shortcut = implode('|', $shortcut);
}
$shortcuts = preg_split('{(\|)-?}', ltrim($shortcut, '-'));
$shortcuts = array_filter($shortcuts);
$shortcut = implode('|', $shortcuts);
if (empty($shortcut)) {
throw new InvalidArgumentException('An option shortcut cannot be empty.');
}
}
if (null === $mode) {
$mode = self::VALUE_NONE;
} elseif ($mode > 15 || $mode < 1) {
throw new InvalidArgumentException(sprintf('Option mode "%s" is not valid.', $mode));
}
$this->name = $name;
$this->shortcut = $shortcut;
$this->mode = $mode;
$this->description = $description;
if ($this->isArray() && !$this->acceptValue()) {
throw new InvalidArgumentException('Impossible to have an option mode VALUE_IS_ARRAY if the option does not accept a value.');
}
$this->setDefault($default);
}
/**
* Returns the option shortcut.
*
* @return string|null The shortcut
*/
public function getShortcut()
{
return $this->shortcut;
}
/**
* Returns the option name.
*
* @return string The name
*/
public function getName()
{
return $this->name;
}
/**
* Returns true if the option accepts a value.
*
* @return bool true if value mode is not self::VALUE_NONE, false otherwise
*/
public function acceptValue()
{
return $this->isValueRequired() || $this->isValueOptional();
}
/**
* Returns true if the option requires a value.
*
* @return bool true if value mode is self::VALUE_REQUIRED, false otherwise
*/
public function isValueRequired()
{
return self::VALUE_REQUIRED === (self::VALUE_REQUIRED & $this->mode);
}
/**
* Returns true if the option takes an optional value.
*
* @return bool true if value mode is self::VALUE_OPTIONAL, false otherwise
*/
public function isValueOptional()
{
return self::VALUE_OPTIONAL === (self::VALUE_OPTIONAL & $this->mode);
}
/**
* Returns true if the option can take multiple values.
*
* @return bool true if mode is self::VALUE_IS_ARRAY, false otherwise
*/
public function isArray()
{
return self::VALUE_IS_ARRAY === (self::VALUE_IS_ARRAY & $this->mode);
}
/**
* Sets the default value.
*
* @param string|string[]|int|bool|null $default The default value
*
* @throws LogicException When incorrect default value is given
*/
public function setDefault($default = null)
{
if (self::VALUE_NONE === (self::VALUE_NONE & $this->mode) && null !== $default) {
throw new LogicException('Cannot set a default value when using InputOption::VALUE_NONE mode.');
}
if ($this->isArray()) {
if (null === $default) {
$default = [];
} elseif (!\is_array($default)) {
throw new LogicException('A default value for an array option must be an array.');
}
}
$this->default = $this->acceptValue() ? $default : false;
}
/**
* Returns the default value.
*
* @return string|string[]|int|bool|null The default value
*/
public function getDefault()
{
return $this->default;
}
/**
* Returns the description text.
*
* @return string The description text
*/
public function getDescription()
{
return $this->description;
}
/**
* Checks whether the given option equals this one.
*
* @return bool
*/
public function equals(self $option)
{
return $option->getName() === $this->getName()
&& $option->getShortcut() === $this->getShortcut()
&& $option->getDefault() === $this->getDefault()
&& $option->isArray() === $this->isArray()
&& $option->isValueRequired() === $this->isValueRequired()
&& $option->isValueOptional() === $this->isValueOptional()
;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Input;
/**
* InputAwareInterface should be implemented by classes that depends on the
* Console Input.
*
* @author Wouter J <waldio.webdesign@gmail.com>
*/
interface InputAwareInterface
{
/**
* Sets the Console Input.
*/
public function setInput(InputInterface $input);
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Input;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\LogicException;
/**
* Represents a command line argument.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class InputArgument
{
const REQUIRED = 1;
const OPTIONAL = 2;
const IS_ARRAY = 4;
private $name;
private $mode;
private $default;
private $description;
/**
* @param string $name The argument name
* @param int|null $mode The argument mode: self::REQUIRED or self::OPTIONAL
* @param string $description A description text
* @param string|string[]|null $default The default value (for self::OPTIONAL mode only)
*
* @throws InvalidArgumentException When argument mode is not valid
*/
public function __construct(string $name, int $mode = null, string $description = '', $default = null)
{
if (null === $mode) {
$mode = self::OPTIONAL;
} elseif ($mode > 7 || $mode < 1) {
throw new InvalidArgumentException(sprintf('Argument mode "%s" is not valid.', $mode));
}
$this->name = $name;
$this->mode = $mode;
$this->description = $description;
$this->setDefault($default);
}
/**
* Returns the argument name.
*
* @return string The argument name
*/
public function getName()
{
return $this->name;
}
/**
* Returns true if the argument is required.
*
* @return bool true if parameter mode is self::REQUIRED, false otherwise
*/
public function isRequired()
{
return self::REQUIRED === (self::REQUIRED & $this->mode);
}
/**
* Returns true if the argument can take multiple values.
*
* @return bool true if mode is self::IS_ARRAY, false otherwise
*/
public function isArray()
{
return self::IS_ARRAY === (self::IS_ARRAY & $this->mode);
}
/**
* Sets the default value.
*
* @param string|string[]|null $default The default value
*
* @throws LogicException When incorrect default value is given
*/
public function setDefault($default = null)
{
if (self::REQUIRED === $this->mode && null !== $default) {
throw new LogicException('Cannot set a default value except for InputArgument::OPTIONAL mode.');
}
if ($this->isArray()) {
if (null === $default) {
$default = [];
} elseif (!\is_array($default)) {
throw new LogicException('A default value for an array argument must be an array.');
}
}
$this->default = $default;
}
/**
* Returns the default value.
*
* @return string|string[]|null The default value
*/
public function getDefault()
{
return $this->default;
}
/**
* Returns the description text.
*
* @return string The description text
*/
public function getDescription()
{
return $this->description;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Input;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\InvalidOptionException;
/**
* ArrayInput represents an input provided as an array.
*
* Usage:
*
* $input = new ArrayInput(['command' => 'foo:bar', 'foo' => 'bar', '--bar' => 'foobar']);
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class ArrayInput extends Input
{
private $parameters;
public function __construct(array $parameters, InputDefinition $definition = null)
{
$this->parameters = $parameters;
parent::__construct($definition);
}
/**
* {@inheritdoc}
*/
public function getFirstArgument()
{
foreach ($this->parameters as $param => $value) {
if ($param && \is_string($param) && '-' === $param[0]) {
continue;
}
return $value;
}
return null;
}
/**
* {@inheritdoc}
*/
public function hasParameterOption($values, bool $onlyParams = false)
{
$values = (array) $values;
foreach ($this->parameters as $k => $v) {
if (!\is_int($k)) {
$v = $k;
}
if ($onlyParams && '--' === $v) {
return false;
}
if (\in_array($v, $values)) {
return true;
}
}
return false;
}
/**
* {@inheritdoc}
*/
public function getParameterOption($values, $default = false, bool $onlyParams = false)
{
$values = (array) $values;
foreach ($this->parameters as $k => $v) {
if ($onlyParams && ('--' === $k || (\is_int($k) && '--' === $v))) {
return $default;
}
if (\is_int($k)) {
if (\in_array($v, $values)) {
return true;
}
} elseif (\in_array($k, $values)) {
return $v;
}
}
return $default;
}
/**
* Returns a stringified representation of the args passed to the command.
*
* @return string
*/
public function __toString()
{
$params = [];
foreach ($this->parameters as $param => $val) {
if ($param && \is_string($param) && '-' === $param[0]) {
if (\is_array($val)) {
foreach ($val as $v) {
$params[] = $param.('' != $v ? '='.$this->escapeToken($v) : '');
}
} else {
$params[] = $param.('' != $val ? '='.$this->escapeToken($val) : '');
}
} else {
$params[] = \is_array($val) ? implode(' ', array_map([$this, 'escapeToken'], $val)) : $this->escapeToken($val);
}
}
return implode(' ', $params);
}
/**
* {@inheritdoc}
*/
protected function parse()
{
foreach ($this->parameters as $key => $value) {
if ('--' === $key) {
return;
}
if (0 === strpos($key, '--')) {
$this->addLongOption(substr($key, 2), $value);
} elseif (0 === strpos($key, '-')) {
$this->addShortOption(substr($key, 1), $value);
} else {
$this->addArgument($key, $value);
}
}
}
/**
* Adds a short option value.
*
* @throws InvalidOptionException When option given doesn't exist
*/
private function addShortOption(string $shortcut, $value)
{
if (!$this->definition->hasShortcut($shortcut)) {
throw new InvalidOptionException(sprintf('The "-%s" option does not exist.', $shortcut));
}
$this->addLongOption($this->definition->getOptionForShortcut($shortcut)->getName(), $value);
}
/**
* Adds a long option value.
*
* @throws InvalidOptionException When option given doesn't exist
* @throws InvalidOptionException When a required value is missing
*/
private function addLongOption(string $name, $value)
{
if (!$this->definition->hasOption($name)) {
throw new InvalidOptionException(sprintf('The "--%s" option does not exist.', $name));
}
$option = $this->definition->getOption($name);
if (null === $value) {
if ($option->isValueRequired()) {
throw new InvalidOptionException(sprintf('The "--%s" option requires a value.', $name));
}
if (!$option->isValueOptional()) {
$value = true;
}
}
$this->options[$name] = $value;
}
/**
* Adds an argument value.
*
* @param string|int $name The argument name
* @param mixed $value The value for the argument
*
* @throws InvalidArgumentException When argument given doesn't exist
*/
private function addArgument($name, $value)
{
if (!$this->definition->hasArgument($name)) {
throw new InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name));
}
$this->arguments[$name] = $value;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Input;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\LogicException;
/**
* A InputDefinition represents a set of valid command line arguments and options.
*
* Usage:
*
* $definition = new InputDefinition([
* new InputArgument('name', InputArgument::REQUIRED),
* new InputOption('foo', 'f', InputOption::VALUE_REQUIRED),
* ]);
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class InputDefinition
{
private $arguments;
private $requiredCount;
private $hasAnArrayArgument = false;
private $hasOptional;
private $options;
private $shortcuts;
/**
* @param array $definition An array of InputArgument and InputOption instance
*/
public function __construct(array $definition = [])
{
$this->setDefinition($definition);
}
/**
* Sets the definition of the input.
*/
public function setDefinition(array $definition)
{
$arguments = [];
$options = [];
foreach ($definition as $item) {
if ($item instanceof InputOption) {
$options[] = $item;
} else {
$arguments[] = $item;
}
}
$this->setArguments($arguments);
$this->setOptions($options);
}
/**
* Sets the InputArgument objects.
*
* @param InputArgument[] $arguments An array of InputArgument objects
*/
public function setArguments(array $arguments = [])
{
$this->arguments = [];
$this->requiredCount = 0;
$this->hasOptional = false;
$this->hasAnArrayArgument = false;
$this->addArguments($arguments);
}
/**
* Adds an array of InputArgument objects.
*
* @param InputArgument[] $arguments An array of InputArgument objects
*/
public function addArguments(?array $arguments = [])
{
if (null !== $arguments) {
foreach ($arguments as $argument) {
$this->addArgument($argument);
}
}
}
/**
* @throws LogicException When incorrect argument is given
*/
public function addArgument(InputArgument $argument)
{
if (isset($this->arguments[$argument->getName()])) {
throw new LogicException(sprintf('An argument with name "%s" already exists.', $argument->getName()));
}
if ($this->hasAnArrayArgument) {
throw new LogicException('Cannot add an argument after an array argument.');
}
if ($argument->isRequired() && $this->hasOptional) {
throw new LogicException('Cannot add a required argument after an optional one.');
}
if ($argument->isArray()) {
$this->hasAnArrayArgument = true;
}
if ($argument->isRequired()) {
++$this->requiredCount;
} else {
$this->hasOptional = true;
}
$this->arguments[$argument->getName()] = $argument;
}
/**
* Returns an InputArgument by name or by position.
*
* @param string|int $name The InputArgument name or position
*
* @return InputArgument An InputArgument object
*
* @throws InvalidArgumentException When argument given doesn't exist
*/
public function getArgument($name)
{
if (!$this->hasArgument($name)) {
throw new InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name));
}
$arguments = \is_int($name) ? array_values($this->arguments) : $this->arguments;
return $arguments[$name];
}
/**
* Returns true if an InputArgument object exists by name or position.
*
* @param string|int $name The InputArgument name or position
*
* @return bool true if the InputArgument object exists, false otherwise
*/
public function hasArgument($name)
{
$arguments = \is_int($name) ? array_values($this->arguments) : $this->arguments;
return isset($arguments[$name]);
}
/**
* Gets the array of InputArgument objects.
*
* @return InputArgument[] An array of InputArgument objects
*/
public function getArguments()
{
return $this->arguments;
}
/**
* Returns the number of InputArguments.
*
* @return int The number of InputArguments
*/
public function getArgumentCount()
{
return $this->hasAnArrayArgument ? \PHP_INT_MAX : \count($this->arguments);
}
/**
* Returns the number of required InputArguments.
*
* @return int The number of required InputArguments
*/
public function getArgumentRequiredCount()
{
return $this->requiredCount;
}
/**
* Gets the default values.
*
* @return array An array of default values
*/
public function getArgumentDefaults()
{
$values = [];
foreach ($this->arguments as $argument) {
$values[$argument->getName()] = $argument->getDefault();
}
return $values;
}
/**
* Sets the InputOption objects.
*
* @param InputOption[] $options An array of InputOption objects
*/
public function setOptions(array $options = [])
{
$this->options = [];
$this->shortcuts = [];
$this->addOptions($options);
}
/**
* Adds an array of InputOption objects.
*
* @param InputOption[] $options An array of InputOption objects
*/
public function addOptions(array $options = [])
{
foreach ($options as $option) {
$this->addOption($option);
}
}
/**
* @throws LogicException When option given already exist
*/
public function addOption(InputOption $option)
{
if (isset($this->options[$option->getName()]) && !$option->equals($this->options[$option->getName()])) {
throw new LogicException(sprintf('An option named "%s" already exists.', $option->getName()));
}
if ($option->getShortcut()) {
foreach (explode('|', $option->getShortcut()) as $shortcut) {
if (isset($this->shortcuts[$shortcut]) && !$option->equals($this->options[$this->shortcuts[$shortcut]])) {
throw new LogicException(sprintf('An option with shortcut "%s" already exists.', $shortcut));
}
}
}
$this->options[$option->getName()] = $option;
if ($option->getShortcut()) {
foreach (explode('|', $option->getShortcut()) as $shortcut) {
$this->shortcuts[$shortcut] = $option->getName();
}
}
}
/**
* Returns an InputOption by name.
*
* @return InputOption A InputOption object
*
* @throws InvalidArgumentException When option given doesn't exist
*/
public function getOption(string $name)
{
if (!$this->hasOption($name)) {
throw new InvalidArgumentException(sprintf('The "--%s" option does not exist.', $name));
}
return $this->options[$name];
}
/**
* Returns true if an InputOption object exists by name.
*
* This method can't be used to check if the user included the option when
* executing the command (use getOption() instead).
*
* @return bool true if the InputOption object exists, false otherwise
*/
public function hasOption(string $name)
{
return isset($this->options[$name]);
}
/**
* Gets the array of InputOption objects.
*
* @return InputOption[] An array of InputOption objects
*/
public function getOptions()
{
return $this->options;
}
/**
* Returns true if an InputOption object exists by shortcut.
*
* @return bool true if the InputOption object exists, false otherwise
*/
public function hasShortcut(string $name)
{
return isset($this->shortcuts[$name]);
}
/**
* Gets an InputOption by shortcut.
*
* @return InputOption An InputOption object
*/
public function getOptionForShortcut(string $shortcut)
{
return $this->getOption($this->shortcutToName($shortcut));
}
/**
* Gets an array of default values.
*
* @return array An array of all default values
*/
public function getOptionDefaults()
{
$values = [];
foreach ($this->options as $option) {
$values[$option->getName()] = $option->getDefault();
}
return $values;
}
/**
* Returns the InputOption name given a shortcut.
*
* @throws InvalidArgumentException When option given does not exist
*
* @internal
*/
public function shortcutToName(string $shortcut): string
{
if (!isset($this->shortcuts[$shortcut])) {
throw new InvalidArgumentException(sprintf('The "-%s" option does not exist.', $shortcut));
}
return $this->shortcuts[$shortcut];
}
/**
* Gets the synopsis.
*
* @return string The synopsis
*/
public function getSynopsis(bool $short = false)
{
$elements = [];
if ($short && $this->getOptions()) {
$elements[] = '[options]';
} elseif (!$short) {
foreach ($this->getOptions() as $option) {
$value = '';
if ($option->acceptValue()) {
$value = sprintf(
' %s%s%s',
$option->isValueOptional() ? '[' : '',
strtoupper($option->getName()),
$option->isValueOptional() ? ']' : ''
);
}
$shortcut = $option->getShortcut() ? sprintf('-%s|', $option->getShortcut()) : '';
$elements[] = sprintf('[%s--%s%s]', $shortcut, $option->getName(), $value);
}
}
if (\count($elements) && $this->getArguments()) {
$elements[] = '[--]';
}
$tail = '';
foreach ($this->getArguments() as $argument) {
$element = '<'.$argument->getName().'>';
if ($argument->isArray()) {
$element .= '...';
}
if (!$argument->isRequired()) {
$element = '['.$element;
$tail .= ']';
}
$elements[] = $element;
}
return implode(' ', $elements).$tail;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Input;
use Symfony\Component\Console\Exception\InvalidArgumentException;
/**
* StringInput represents an input provided as a string.
*
* Usage:
*
* $input = new StringInput('foo --bar="foobar"');
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class StringInput extends ArgvInput
{
const REGEX_STRING = '([^\s]+?)(?:\s|(?<!\\\\)"|(?<!\\\\)\'|$)';
const REGEX_QUOTED_STRING = '(?:"([^"\\\\]*(?:\\\\.[^"\\\\]*)*)"|\'([^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\')';
/**
* @param string $input A string representing the parameters from the CLI
*/
public function __construct(string $input)
{
parent::__construct([]);
$this->setTokens($this->tokenize($input));
}
/**
* Tokenizes a string.
*
* @throws InvalidArgumentException When unable to parse input (should never happen)
*/
private function tokenize(string $input): array
{
$tokens = [];
$length = \strlen($input);
$cursor = 0;
while ($cursor < $length) {
if (preg_match('/\s+/A', $input, $match, null, $cursor)) {
} elseif (preg_match('/([^="\'\s]+?)(=?)('.self::REGEX_QUOTED_STRING.'+)/A', $input, $match, null, $cursor)) {
$tokens[] = $match[1].$match[2].stripcslashes(str_replace(['"\'', '\'"', '\'\'', '""'], '', substr($match[3], 1, \strlen($match[3]) - 2)));
} elseif (preg_match('/'.self::REGEX_QUOTED_STRING.'/A', $input, $match, null, $cursor)) {
$tokens[] = stripcslashes(substr($match[0], 1, \strlen($match[0]) - 2));
} elseif (preg_match('/'.self::REGEX_STRING.'/A', $input, $match, null, $cursor)) {
$tokens[] = stripcslashes($match[1]);
} else {
// should never happen
throw new InvalidArgumentException(sprintf('Unable to parse input near "... %s ...".', substr($input, $cursor, 10)));
}
$cursor += \strlen($match[0]);
}
return $tokens;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Input;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\RuntimeException;
/**
* Input is the base class for all concrete Input classes.
*
* Three concrete classes are provided by default:
*
* * `ArgvInput`: The input comes from the CLI arguments (argv)
* * `StringInput`: The input is provided as a string
* * `ArrayInput`: The input is provided as an array
*
* @author Fabien Potencier <fabien@symfony.com>
*/
abstract class Input implements InputInterface, StreamableInputInterface
{
protected $definition;
protected $stream;
protected $options = [];
protected $arguments = [];
protected $interactive = true;
public function __construct(InputDefinition $definition = null)
{
if (null === $definition) {
$this->definition = new InputDefinition();
} else {
$this->bind($definition);
$this->validate();
}
}
/**
* {@inheritdoc}
*/
public function bind(InputDefinition $definition)
{
$this->arguments = [];
$this->options = [];
$this->definition = $definition;
$this->parse();
}
/**
* Processes command line arguments.
*/
abstract protected function parse();
/**
* {@inheritdoc}
*/
public function validate()
{
$definition = $this->definition;
$givenArguments = $this->arguments;
$missingArguments = array_filter(array_keys($definition->getArguments()), function ($argument) use ($definition, $givenArguments) {
return !\array_key_exists($argument, $givenArguments) && $definition->getArgument($argument)->isRequired();
});
if (\count($missingArguments) > 0) {
throw new RuntimeException(sprintf('Not enough arguments (missing: "%s").', implode(', ', $missingArguments)));
}
}
/**
* {@inheritdoc}
*/
public function isInteractive()
{
return $this->interactive;
}
/**
* {@inheritdoc}
*/
public function setInteractive(bool $interactive)
{
$this->interactive = $interactive;
}
/**
* {@inheritdoc}
*/
public function getArguments()
{
return array_merge($this->definition->getArgumentDefaults(), $this->arguments);
}
/**
* {@inheritdoc}
*/
public function getArgument(string $name)
{
if (!$this->definition->hasArgument($name)) {
throw new InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name));
}
return isset($this->arguments[$name]) ? $this->arguments[$name] : $this->definition->getArgument($name)->getDefault();
}
/**
* {@inheritdoc}
*/
public function setArgument(string $name, $value)
{
if (!$this->definition->hasArgument($name)) {
throw new InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name));
}
$this->arguments[$name] = $value;
}
/**
* {@inheritdoc}
*/
public function hasArgument($name)
{
return $this->definition->hasArgument($name);
}
/**
* {@inheritdoc}
*/
public function getOptions()
{
return array_merge($this->definition->getOptionDefaults(), $this->options);
}
/**
* {@inheritdoc}
*/
public function getOption(string $name)
{
if (!$this->definition->hasOption($name)) {
throw new InvalidArgumentException(sprintf('The "%s" option does not exist.', $name));
}
return \array_key_exists($name, $this->options) ? $this->options[$name] : $this->definition->getOption($name)->getDefault();
}
/**
* {@inheritdoc}
*/
public function setOption(string $name, $value)
{
if (!$this->definition->hasOption($name)) {
throw new InvalidArgumentException(sprintf('The "%s" option does not exist.', $name));
}
$this->options[$name] = $value;
}
/**
* {@inheritdoc}
*/
public function hasOption(string $name)
{
return $this->definition->hasOption($name);
}
/**
* Escapes a token through escapeshellarg if it contains unsafe chars.
*
* @return string
*/
public function escapeToken(string $token)
{
return preg_match('{^[\w-]+$}', $token) ? $token : escapeshellarg($token);
}
/**
* {@inheritdoc}
*/
public function setStream($stream)
{
$this->stream = $stream;
}
/**
* {@inheritdoc}
*/
public function getStream()
{
return $this->stream;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Input;
use Symfony\Component\Console\Exception\RuntimeException;
/**
* ArgvInput represents an input coming from the CLI arguments.
*
* Usage:
*
* $input = new ArgvInput();
*
* By default, the `$_SERVER['argv']` array is used for the input values.
*
* This can be overridden by explicitly passing the input values in the constructor:
*
* $input = new ArgvInput($_SERVER['argv']);
*
* If you pass it yourself, don't forget that the first element of the array
* is the name of the running application.
*
* When passing an argument to the constructor, be sure that it respects
* the same rules as the argv one. It's almost always better to use the
* `StringInput` when you want to provide your own input.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @see http://www.gnu.org/software/libc/manual/html_node/Argument-Syntax.html
* @see http://www.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap12.html#tag_12_02
*/
class ArgvInput extends Input
{
private $tokens;
private $parsed;
public function __construct(array $argv = null, InputDefinition $definition = null)
{
$argv = $argv ?? $_SERVER['argv'] ?? [];
// strip the application name
array_shift($argv);
$this->tokens = $argv;
parent::__construct($definition);
}
protected function setTokens(array $tokens)
{
$this->tokens = $tokens;
}
/**
* {@inheritdoc}
*/
protected function parse()
{
$parseOptions = true;
$this->parsed = $this->tokens;
while (null !== $token = array_shift($this->parsed)) {
if ($parseOptions && '' == $token) {
$this->parseArgument($token);
} elseif ($parseOptions && '--' == $token) {
$parseOptions = false;
} elseif ($parseOptions && 0 === strpos($token, '--')) {
$this->parseLongOption($token);
} elseif ($parseOptions && '-' === $token[0] && '-' !== $token) {
$this->parseShortOption($token);
} else {
$this->parseArgument($token);
}
}
}
/**
* Parses a short option.
*/
private function parseShortOption(string $token)
{
$name = substr($token, 1);
if (\strlen($name) > 1) {
if ($this->definition->hasShortcut($name[0]) && $this->definition->getOptionForShortcut($name[0])->acceptValue()) {
// an option with a value (with no space)
$this->addShortOption($name[0], substr($name, 1));
} else {
$this->parseShortOptionSet($name);
}
} else {
$this->addShortOption($name, null);
}
}
/**
* Parses a short option set.
*
* @throws RuntimeException When option given doesn't exist
*/
private function parseShortOptionSet(string $name)
{
$len = \strlen($name);
for ($i = 0; $i < $len; ++$i) {
if (!$this->definition->hasShortcut($name[$i])) {
$encoding = mb_detect_encoding($name, null, true);
throw new RuntimeException(sprintf('The "-%s" option does not exist.', false === $encoding ? $name[$i] : mb_substr($name, $i, 1, $encoding)));
}
$option = $this->definition->getOptionForShortcut($name[$i]);
if ($option->acceptValue()) {
$this->addLongOption($option->getName(), $i === $len - 1 ? null : substr($name, $i + 1));
break;
} else {
$this->addLongOption($option->getName(), null);
}
}
}
/**
* Parses a long option.
*/
private function parseLongOption(string $token)
{
$name = substr($token, 2);
if (false !== $pos = strpos($name, '=')) {
if (0 === \strlen($value = substr($name, $pos + 1))) {
array_unshift($this->parsed, $value);
}
$this->addLongOption(substr($name, 0, $pos), $value);
} else {
$this->addLongOption($name, null);
}
}
/**
* Parses an argument.
*
* @throws RuntimeException When too many arguments are given
*/
private function parseArgument(string $token)
{
$c = \count($this->arguments);
// if input is expecting another argument, add it
if ($this->definition->hasArgument($c)) {
$arg = $this->definition->getArgument($c);
$this->arguments[$arg->getName()] = $arg->isArray() ? [$token] : $token;
// if last argument isArray(), append token to last argument
} elseif ($this->definition->hasArgument($c - 1) && $this->definition->getArgument($c - 1)->isArray()) {
$arg = $this->definition->getArgument($c - 1);
$this->arguments[$arg->getName()][] = $token;
// unexpected argument
} else {
$all = $this->definition->getArguments();
$symfonyCommandName = null;
if (($inputArgument = $all[$key = array_key_first($all)] ?? null) && 'command' === $inputArgument->getName()) {
$symfonyCommandName = $this->arguments['command'] ?? null;
unset($all[$key]);
}
if (\count($all)) {
if ($symfonyCommandName) {
$message = sprintf('Too many arguments to "%s" command, expected arguments "%s".', $symfonyCommandName, implode('" "', array_keys($all)));
} else {
$message = sprintf('Too many arguments, expected arguments "%s".', implode('" "', array_keys($all)));
}
} elseif ($symfonyCommandName) {
$message = sprintf('No arguments expected for "%s" command, got "%s".', $symfonyCommandName, $token);
} else {
$message = sprintf('No arguments expected, got "%s".', $token);
}
throw new RuntimeException($message);
}
}
/**
* Adds a short option value.
*
* @throws RuntimeException When option given doesn't exist
*/
private function addShortOption(string $shortcut, $value)
{
if (!$this->definition->hasShortcut($shortcut)) {
throw new RuntimeException(sprintf('The "-%s" option does not exist.', $shortcut));
}
$this->addLongOption($this->definition->getOptionForShortcut($shortcut)->getName(), $value);
}
/**
* Adds a long option value.
*
* @throws RuntimeException When option given doesn't exist
*/
private function addLongOption(string $name, $value)
{
if (!$this->definition->hasOption($name)) {
throw new RuntimeException(sprintf('The "--%s" option does not exist.', $name));
}
$option = $this->definition->getOption($name);
if (null !== $value && !$option->acceptValue()) {
throw new RuntimeException(sprintf('The "--%s" option does not accept a value.', $name));
}
if (\in_array($value, ['', null], true) && $option->acceptValue() && \count($this->parsed)) {
// if option accepts an optional or mandatory argument
// let's see if there is one provided
$next = array_shift($this->parsed);
if ((isset($next[0]) && '-' !== $next[0]) || \in_array($next, ['', null], true)) {
$value = $next;
} else {
array_unshift($this->parsed, $next);
}
}
if (null === $value) {
if ($option->isValueRequired()) {
throw new RuntimeException(sprintf('The "--%s" option requires a value.', $name));
}
if (!$option->isArray() && !$option->isValueOptional()) {
$value = true;
}
}
if ($option->isArray()) {
$this->options[$name][] = $value;
} else {
$this->options[$name] = $value;
}
}
/**
* {@inheritdoc}
*/
public function getFirstArgument()
{
$isOption = false;
foreach ($this->tokens as $i => $token) {
if ($token && '-' === $token[0]) {
if (false !== strpos($token, '=') || !isset($this->tokens[$i + 1])) {
continue;
}
// If it's a long option, consider that everything after "--" is the option name.
// Otherwise, use the last char (if it's a short option set, only the last one can take a value with space separator)
$name = '-' === $token[1] ? substr($token, 2) : substr($token, -1);
if (!isset($this->options[$name]) && !$this->definition->hasShortcut($name)) {
// noop
} elseif ((isset($this->options[$name]) || isset($this->options[$name = $this->definition->shortcutToName($name)])) && $this->tokens[$i + 1] === $this->options[$name]) {
$isOption = true;
}
continue;
}
if ($isOption) {
$isOption = false;
continue;
}
return $token;
}
return null;
}
/**
* {@inheritdoc}
*/
public function hasParameterOption($values, bool $onlyParams = false)
{
$values = (array) $values;
foreach ($this->tokens as $token) {
if ($onlyParams && '--' === $token) {
return false;
}
foreach ($values as $value) {
// Options with values:
// For long options, test for '--option=' at beginning
// For short options, test for '-o' at beginning
$leading = 0 === strpos($value, '--') ? $value.'=' : $value;
if ($token === $value || '' !== $leading && 0 === strpos($token, $leading)) {
return true;
}
}
}
return false;
}
/**
* {@inheritdoc}
*/
public function getParameterOption($values, $default = false, bool $onlyParams = false)
{
$values = (array) $values;
$tokens = $this->tokens;
while (0 < \count($tokens)) {
$token = array_shift($tokens);
if ($onlyParams && '--' === $token) {
return $default;
}
foreach ($values as $value) {
if ($token === $value) {
return array_shift($tokens);
}
// Options with values:
// For long options, test for '--option=' at beginning
// For short options, test for '-o' at beginning
$leading = 0 === strpos($value, '--') ? $value.'=' : $value;
if ('' !== $leading && 0 === strpos($token, $leading)) {
return substr($token, \strlen($leading));
}
}
}
return $default;
}
/**
* Returns a stringified representation of the args passed to the command.
*
* @return string
*/
public function __toString()
{
$tokens = array_map(function ($token) {
if (preg_match('{^(-[^=]+=)(.+)}', $token, $match)) {
return $match[1].$this->escapeToken($match[2]);
}
if ($token && '-' !== $token[0]) {
return $this->escapeToken($token);
}
return $token;
}, $this->tokens);
return implode(' ', $tokens);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Input;
/**
* StreamableInputInterface is the interface implemented by all input classes
* that have an input stream.
*
* @author Robin Chalas <robin.chalas@gmail.com>
*/
interface StreamableInputInterface extends InputInterface
{
/**
* Sets the input stream to read from when interacting with the user.
*
* This is mainly useful for testing purpose.
*
* @param resource $stream The input stream
*/
public function setStream($stream);
/**
* Returns the input stream.
*
* @return resource|null
*/
public function getStream();
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Style;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Helper\Helper;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Helper\SymfonyQuestionHelper;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Helper\TableCell;
use Symfony\Component\Console\Helper\TableSeparator;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Output\TrimmedBufferOutput;
use Symfony\Component\Console\Question\ChoiceQuestion;
use Symfony\Component\Console\Question\ConfirmationQuestion;
use Symfony\Component\Console\Question\Question;
use Symfony\Component\Console\Terminal;
/**
* Output decorator helpers for the Symfony Style Guide.
*
* @author Kevin Bond <kevinbond@gmail.com>
*/
class SymfonyStyle extends OutputStyle
{
const MAX_LINE_LENGTH = 120;
private $input;
private $questionHelper;
private $progressBar;
private $lineLength;
private $bufferedOutput;
public function __construct(InputInterface $input, OutputInterface $output)
{
$this->input = $input;
$this->bufferedOutput = new TrimmedBufferOutput(\DIRECTORY_SEPARATOR === '\\' ? 4 : 2, $output->getVerbosity(), false, clone $output->getFormatter());
// Windows cmd wraps lines as soon as the terminal width is reached, whether there are following chars or not.
$width = (new Terminal())->getWidth() ?: self::MAX_LINE_LENGTH;
$this->lineLength = min($width - (int) (\DIRECTORY_SEPARATOR === '\\'), self::MAX_LINE_LENGTH);
parent::__construct($output);
}
/**
* Formats a message as a block of text.
*
* @param string|array $messages The message to write in the block
*/
public function block($messages, ?string $type = null, ?string $style = null, string $prefix = ' ', bool $padding = false, bool $escape = true)
{
$messages = \is_array($messages) ? array_values($messages) : [$messages];
$this->autoPrependBlock();
$this->writeln($this->createBlock($messages, $type, $style, $prefix, $padding, $escape));
$this->newLine();
}
/**
* {@inheritdoc}
*/
public function title(string $message)
{
$this->autoPrependBlock();
$this->writeln([
sprintf('<comment>%s</>', OutputFormatter::escapeTrailingBackslash($message)),
sprintf('<comment>%s</>', str_repeat('=', Helper::strlenWithoutDecoration($this->getFormatter(), $message))),
]);
$this->newLine();
}
/**
* {@inheritdoc}
*/
public function section(string $message)
{
$this->autoPrependBlock();
$this->writeln([
sprintf('<comment>%s</>', OutputFormatter::escapeTrailingBackslash($message)),
sprintf('<comment>%s</>', str_repeat('-', Helper::strlenWithoutDecoration($this->getFormatter(), $message))),
]);
$this->newLine();
}
/**
* {@inheritdoc}
*/
public function listing(array $elements)
{
$this->autoPrependText();
$elements = array_map(function ($element) {
return sprintf(' * %s', $element);
}, $elements);
$this->writeln($elements);
$this->newLine();
}
/**
* {@inheritdoc}
*/
public function text($message)
{
$this->autoPrependText();
$messages = \is_array($message) ? array_values($message) : [$message];
foreach ($messages as $message) {
$this->writeln(sprintf(' %s', $message));
}
}
/**
* Formats a command comment.
*
* @param string|array $message
*/
public function comment($message)
{
$this->block($message, null, null, '<fg=default;bg=default> // </>', false, false);
}
/**
* {@inheritdoc}
*/
public function success($message)
{
$this->block($message, 'OK', 'fg=black;bg=green', ' ', true);
}
/**
* {@inheritdoc}
*/
public function error($message)
{
$this->block($message, 'ERROR', 'fg=white;bg=red', ' ', true);
}
/**
* {@inheritdoc}
*/
public function warning($message)
{
$this->block($message, 'WARNING', 'fg=black;bg=yellow', ' ', true);
}
/**
* {@inheritdoc}
*/
public function note($message)
{
$this->block($message, 'NOTE', 'fg=yellow', ' ! ');
}
/**
* Formats an info message.
*
* @param string|array $message
*/
public function info($message)
{
$this->block($message, 'INFO', 'fg=green', ' ', true);
}
/**
* {@inheritdoc}
*/
public function caution($message)
{
$this->block($message, 'CAUTION', 'fg=white;bg=red', ' ! ', true);
}
/**
* {@inheritdoc}
*/
public function table(array $headers, array $rows)
{
$style = clone Table::getStyleDefinition('symfony-style-guide');
$style->setCellHeaderFormat('<info>%s</info>');
$table = new Table($this);
$table->setHeaders($headers);
$table->setRows($rows);
$table->setStyle($style);
$table->render();
$this->newLine();
}
/**
* Formats a horizontal table.
*/
public function horizontalTable(array $headers, array $rows)
{
$style = clone Table::getStyleDefinition('symfony-style-guide');
$style->setCellHeaderFormat('<info>%s</info>');
$table = new Table($this);
$table->setHeaders($headers);
$table->setRows($rows);
$table->setStyle($style);
$table->setHorizontal(true);
$table->render();
$this->newLine();
}
/**
* Formats a list of key/value horizontally.
*
* Each row can be one of:
* * 'A title'
* * ['key' => 'value']
* * new TableSeparator()
*
* @param string|array|TableSeparator ...$list
*/
public function definitionList(...$list)
{
$style = clone Table::getStyleDefinition('symfony-style-guide');
$style->setCellHeaderFormat('<info>%s</info>');
$table = new Table($this);
$headers = [];
$row = [];
foreach ($list as $value) {
if ($value instanceof TableSeparator) {
$headers[] = $value;
$row[] = $value;
continue;
}
if (\is_string($value)) {
$headers[] = new TableCell($value, ['colspan' => 2]);
$row[] = null;
continue;
}
if (!\is_array($value)) {
throw new InvalidArgumentException('Value should be an array, string, or an instance of TableSeparator.');
}
$headers[] = key($value);
$row[] = current($value);
}
$table->setHeaders($headers);
$table->setRows([$row]);
$table->setHorizontal();
$table->setStyle($style);
$table->render();
$this->newLine();
}
/**
* {@inheritdoc}
*/
public function ask(string $question, ?string $default = null, $validator = null)
{
$question = new Question($question, $default);
$question->setValidator($validator);
return $this->askQuestion($question);
}
/**
* {@inheritdoc}
*/
public function askHidden(string $question, $validator = null)
{
$question = new Question($question);
$question->setHidden(true);
$question->setValidator($validator);
return $this->askQuestion($question);
}
/**
* {@inheritdoc}
*/
public function confirm($question, $default = true)
{
return $this->askQuestion(new ConfirmationQuestion($question, $default));
}
/**
* {@inheritdoc}
*/
public function choice(string $question, array $choices, $default = null)
{
if (null !== $default) {
$values = array_flip($choices);
$default = isset($values[$default]) ? $values[$default] : $default;
}
return $this->askQuestion(new ChoiceQuestion($question, $choices, $default));
}
/**
* {@inheritdoc}
*/
public function progressStart(int $max = 0)
{
$this->progressBar = $this->createProgressBar($max);
$this->progressBar->start();
}
/**
* {@inheritdoc}
*/
public function progressAdvance(int $step = 1)
{
$this->getProgressBar()->advance($step);
}
/**
* {@inheritdoc}
*/
public function progressFinish()
{
$this->getProgressBar()->finish();
$this->newLine(2);
$this->progressBar = null;
}
/**
* {@inheritdoc}
*/
public function createProgressBar(int $max = 0)
{
$progressBar = parent::createProgressBar($max);
if ('\\' !== \DIRECTORY_SEPARATOR || 'Hyper' === getenv('TERM_PROGRAM')) {
$progressBar->setEmptyBarCharacter('░'); // light shade character \u2591
$progressBar->setProgressCharacter('');
$progressBar->setBarCharacter('▓'); // dark shade character \u2593
}
return $progressBar;
}
/**
* @return mixed
*/
public function askQuestion(Question $question)
{
if ($this->input->isInteractive()) {
$this->autoPrependBlock();
}
if (!$this->questionHelper) {
$this->questionHelper = new SymfonyQuestionHelper();
}
$answer = $this->questionHelper->ask($this->input, $this, $question);
if ($this->input->isInteractive()) {
$this->newLine();
$this->bufferedOutput->write("\n");
}
return $answer;
}
/**
* {@inheritdoc}
*/
public function writeln($messages, int $type = self::OUTPUT_NORMAL)
{
if (!is_iterable($messages)) {
$messages = [$messages];
}
foreach ($messages as $message) {
parent::writeln($message, $type);
$this->writeBuffer($message, true, $type);
}
}
/**
* {@inheritdoc}
*/
public function write($messages, bool $newline = false, int $type = self::OUTPUT_NORMAL)
{
if (!is_iterable($messages)) {
$messages = [$messages];
}
foreach ($messages as $message) {
parent::write($message, $newline, $type);
$this->writeBuffer($message, $newline, $type);
}
}
/**
* {@inheritdoc}
*/
public function newLine(int $count = 1)
{
parent::newLine($count);
$this->bufferedOutput->write(str_repeat("\n", $count));
}
/**
* Returns a new instance which makes use of stderr if available.
*
* @return self
*/
public function getErrorStyle()
{
return new self($this->input, $this->getErrorOutput());
}
private function getProgressBar(): ProgressBar
{
if (!$this->progressBar) {
throw new RuntimeException('The ProgressBar is not started.');
}
return $this->progressBar;
}
private function autoPrependBlock(): void
{
$chars = substr(str_replace(\PHP_EOL, "\n", $this->bufferedOutput->fetch()), -2);
if (!isset($chars[0])) {
$this->newLine(); //empty history, so we should start with a new line.
return;
}
//Prepend new line for each non LF chars (This means no blank line was output before)
$this->newLine(2 - substr_count($chars, "\n"));
}
private function autoPrependText(): void
{
$fetched = $this->bufferedOutput->fetch();
//Prepend new line if last char isn't EOL:
if ("\n" !== substr($fetched, -1)) {
$this->newLine();
}
}
private function writeBuffer(string $message, bool $newLine, int $type): void
{
// We need to know if the last chars are PHP_EOL
$this->bufferedOutput->write($message, $newLine, $type);
}
private function createBlock(iterable $messages, string $type = null, string $style = null, string $prefix = ' ', bool $padding = false, bool $escape = false): array
{
$indentLength = 0;
$prefixLength = Helper::strlenWithoutDecoration($this->getFormatter(), $prefix);
$lines = [];
if (null !== $type) {
$type = sprintf('[%s] ', $type);
$indentLength = \strlen($type);
$lineIndentation = str_repeat(' ', $indentLength);
}
// wrap and add newlines for each element
foreach ($messages as $key => $message) {
if ($escape) {
$message = OutputFormatter::escape($message);
}
$lines = array_merge($lines, explode(\PHP_EOL, wordwrap($message, $this->lineLength - $prefixLength - $indentLength, \PHP_EOL, true)));
if (\count($messages) > 1 && $key < \count($messages) - 1) {
$lines[] = '';
}
}
$firstLineIndex = 0;
if ($padding && $this->isDecorated()) {
$firstLineIndex = 1;
array_unshift($lines, '');
$lines[] = '';
}
foreach ($lines as $i => &$line) {
if (null !== $type) {
$line = $firstLineIndex === $i ? $type.$line : $lineIndentation.$line;
}
$line = $prefix.$line;
$line .= str_repeat(' ', $this->lineLength - Helper::strlenWithoutDecoration($this->getFormatter(), $line));
if ($style) {
$line = sprintf('<%s>%s</>', $style, $line);
}
}
return $lines;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Style;
use Symfony\Component\Console\Formatter\OutputFormatterInterface;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Decorates output to add console style guide helpers.
*
* @author Kevin Bond <kevinbond@gmail.com>
*/
abstract class OutputStyle implements OutputInterface, StyleInterface
{
private $output;
public function __construct(OutputInterface $output)
{
$this->output = $output;
}
/**
* {@inheritdoc}
*/
public function newLine(int $count = 1)
{
$this->output->write(str_repeat(\PHP_EOL, $count));
}
/**
* @return ProgressBar
*/
public function createProgressBar(int $max = 0)
{
return new ProgressBar($this->output, $max);
}
/**
* {@inheritdoc}
*/
public function write($messages, bool $newline = false, int $type = self::OUTPUT_NORMAL)
{
$this->output->write($messages, $newline, $type);
}
/**
* {@inheritdoc}
*/
public function writeln($messages, int $type = self::OUTPUT_NORMAL)
{
$this->output->writeln($messages, $type);
}
/**
* {@inheritdoc}
*/
public function setVerbosity(int $level)
{
$this->output->setVerbosity($level);
}
/**
* {@inheritdoc}
*/
public function getVerbosity()
{
return $this->output->getVerbosity();
}
/**
* {@inheritdoc}
*/
public function setDecorated(bool $decorated)
{
$this->output->setDecorated($decorated);
}
/**
* {@inheritdoc}
*/
public function isDecorated()
{
return $this->output->isDecorated();
}
/**
* {@inheritdoc}
*/
public function setFormatter(OutputFormatterInterface $formatter)
{
$this->output->setFormatter($formatter);
}
/**
* {@inheritdoc}
*/
public function getFormatter()
{
return $this->output->getFormatter();
}
/**
* {@inheritdoc}
*/
public function isQuiet()
{
return $this->output->isQuiet();
}
/**
* {@inheritdoc}
*/
public function isVerbose()
{
return $this->output->isVerbose();
}
/**
* {@inheritdoc}
*/
public function isVeryVerbose()
{
return $this->output->isVeryVerbose();
}
/**
* {@inheritdoc}
*/
public function isDebug()
{
return $this->output->isDebug();
}
protected function getErrorOutput()
{
if (!$this->output instanceof ConsoleOutputInterface) {
return $this->output;
}
return $this->output->getErrorOutput();
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Style;
/**
* Output style helpers.
*
* @author Kevin Bond <kevinbond@gmail.com>
*/
interface StyleInterface
{
/**
* Formats a command title.
*/
public function title(string $message);
/**
* Formats a section title.
*/
public function section(string $message);
/**
* Formats a list.
*/
public function listing(array $elements);
/**
* Formats informational text.
*
* @param string|array $message
*/
public function text($message);
/**
* Formats a success result bar.
*
* @param string|array $message
*/
public function success($message);
/**
* Formats an error result bar.
*
* @param string|array $message
*/
public function error($message);
/**
* Formats an warning result bar.
*
* @param string|array $message
*/
public function warning($message);
/**
* Formats a note admonition.
*
* @param string|array $message
*/
public function note($message);
/**
* Formats a caution admonition.
*
* @param string|array $message
*/
public function caution($message);
/**
* Formats a table.
*/
public function table(array $headers, array $rows);
/**
* Asks a question.
*
* @return mixed
*/
public function ask(string $question, ?string $default = null, callable $validator = null);
/**
* Asks a question with the user input hidden.
*
* @return mixed
*/
public function askHidden(string $question, callable $validator = null);
/**
* Asks for confirmation.
*
* @return bool
*/
public function confirm(string $question, bool $default = true);
/**
* Asks a choice question.
*
* @param string|int|null $default
*
* @return mixed
*/
public function choice(string $question, array $choices, $default = null);
/**
* Add newline(s).
*/
public function newLine(int $count = 1);
/**
* Starts the progress output.
*/
public function progressStart(int $max = 0);
/**
* Advances the progress output X steps.
*/
public function progressAdvance(int $step = 1);
/**
* Finishes the progress output.
*/
public function progressFinish();
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\SignalRegistry;
final class SignalRegistry
{
private $signalHandlers = [];
public function __construct()
{
if (\function_exists('pcntl_async_signals')) {
pcntl_async_signals(true);
}
}
public function register(int $signal, callable $signalHandler): void
{
if (!isset($this->signalHandlers[$signal])) {
$previousCallback = pcntl_signal_get_handler($signal);
if (\is_callable($previousCallback)) {
$this->signalHandlers[$signal][] = $previousCallback;
}
}
$this->signalHandlers[$signal][] = $signalHandler;
pcntl_signal($signal, [$this, 'handle']);
}
public static function isSupported(): bool
{
if (!\function_exists('pcntl_signal')) {
return false;
}
if (\in_array('pcntl_signal', explode(',', ini_get('disable_functions')))) {
return false;
}
return true;
}
/**
* @internal
*/
public function handle(int $signal): void
{
$count = \count($this->signalHandlers[$signal]);
foreach ($this->signalHandlers[$signal] as $i => $signalHandler) {
$hasNext = $i !== $count - 1;
$signalHandler($signal, $hasNext);
}
}
}
MZ<90><00><><00>@<00><1F><00> <09>!<21>L<>!This program cannot be run in DOS mode.
$<>,<2C>;<3B>B<A7>;<3B>B<A7>;<3B>B<A7>2<9E>מ:<3A>B<A7>2<9E><32><DF>-<2D>B<A7>2<9E>ƞ9<C69E>B<A7>2<9E>ў?<3F>B<A7>a9<61>8<9E>B<A7>;<3B>C<A7><08>B<A7>2<9E>Ȟ:<3A>B<A7>2<9E>֞:<3A>B<A7>2<9E>Ӟ:<3A>B<A7>Rich;<3B>B<A7>PEL<00>MoO<00> 
8 @`?<3F>@<40><00>"P@ Pp!8!@ <00>.text 
 `.rdata<00>
@@.data<00>0@<00>.rsrc @@@.reloc<00>P"@Bj$<24><>@<00>xj<><6A> @<00>e<83><00><><8B>E<8D>PV<50> @<00><45><D083>PV<50> @<00>M<8D><4D>X @<00>e<83><00>E<8D>P<D4>5H @<00>L @YY<59>5\ @<00>E<8D>P<D4>5` @<00>D @YY<59><59><8B>P @<00>M<83><4D><FC>M<8D><4D>T @3<><33>H<00>; 0@u<02><><F3><C3>h<>@<00><><00>l3@<00>$40@<00>5h3@<00>40@h$0@h(0@h 0@<00><15> @<00><><14>00@<00><>}j<08><>Y<>jh"@<00>3ۉ]<5D>d<FC><00>p<04>]俀3@SVW<56>0 @;<3B>t;<3B>u3<>F<F6>u<89><75>h<><00>4 @<00><>3<DA>F<F6>|3@;<3B>u
j<1F>\Y<>;<3B>|3@<00><>u,<2C>5|3@h<> @h<> @<00><>YY<59><59>t<17>E<C7><45><FC><FE><FF><FF><FF><00><><00>5<0@<00>|3@;<3B>uh<> @h<> @<00>lYY<59>|3@9]<5D>uSW<53>8 @9<1D>3@th<>3@<00><>Y<><59>t
SjS<><15>3@<00>$0@<00> <0A> @<00><01>5$0@<00>5(0@<00>5 0@<00><10><><FE><FF><FF> <0C>80@9,0@u7P<37><15> @<00>E<8B><45><08> <09>M<89>PQ<50><51>YYËe<C38B><65>E<8B><45>80@3<>9,0@uP<>h @9<0@u<06><15> @<00>E<C7><45><FC><FE><FF><FF>80@<00><>øMZf9@t3<><33>M<EB><@<00><>@<00>8PEu<><0F>H<18><> t<1B><> <75><D583>v<>3<CC>9<C9><39><00><0E>xtv<>3<BC>9<C9><39><0F><><95><C1>j<01>,0@<00>p @j<><6A>l @YY<59><59>3@<00><>3@<00><15> @<00> t3@<00><08><15> @<00> p3@<00><08><> @<00><00>x3@<00>V<00><><00>=0@u h<>@<00><15> @Y<>g<00>=0@<00>u j<><6A><15> @Y3<59><33><C0>{<00><><E9><9F><FD><FF><FF>U<FF><55><8B><EC>(<00>H1@<00> D1@<00>@1@<00><1@<00>581@<00>=41@f<>`1@f<> T1@f<>01@f<>,1@f<>%(1@f<>-$1@<00><>X1@<00>E<00>L1@<00>E<04>P1@<00>E<08>\1@<00><><8B><85><E0><FC><FF><05>0@<00>P1@<00>L0@<00>@0@ <00><>D0@<00>0@<00><><89><85><D8><FC><FF>0@<00><><89><85><DC><FC><FF> @<00><>0@j<01>?Yj<00> @h!@<00>$ @<00>=<3D>0@uj<01>Yh <00><>( @P<>, @<00>Ë<C9>U<FF><55><8B>E<08><00>8csm<73>u*<2A>xu$<24>@= <05>t=!<05>t="<05>t=@<40>u<05><>3<>]<5D>hH@<00> @3<><33><C0>%<25> @jh("@<00>b<00>5<FF>3@<00>5<8B> @<00><>Y<D6>E<89><45><E4><83>u <0C>u<08><15> @Y<>gj<08><>Y<>e<83><00>5<FF>3@<00>։E<D689><45>5<FF>3@<00><>YY<59>E<89><45>E<8D>P<E0>E<8D>P<E4>u<08>5l @<00><>YP<59>U<00>E<89><45>u<FF><75>֣<FF>3@<00>u<FF><75>փ<FF><14><>3@<00>E<C7><45><FC><FE><FF><FF> <00>E<8B><45><00>j<08><59>U<FF><55><8B>u<08>N<E8><4E><FF><FF><FF><1B><><C0>YH]Ë<>V<FF><56>!@<00><>!@W<><57>;<3B>s<0F><07><>t<02>Ѓ<FF>;<3B>r<FE>_^Ë<>V<FF>"@<00>"@W<><57>;<3B>s<0F><07><>t<02>Ѓ<FF>;<3B>r<FE>_^<5E><>%<25> @<00><><CC>̋<CC>U<FF><55><8B>M<08>MZf9t3<>]ËA<<03><>8PEu<> f9H<0F>‹<94>]<5D><><C3><CC><CC><CC><CC><CC><CC><CC><CC>̋<CC>U<FF><55><8B>E<08>H<<03><0F>ASV<0F>q3<>W<D2>D<18><>v<1B>} <0C>H ;<3B>r <09>X<03>;<3B>r
B<83><C0>(;<3B>r<D6>3<E8>_^[]<5D><><C3><CC><CC><CC><CC><CC><CC><CC><CC><CC>̋<CC>U<FF><55>j<EC>hH"@he@d<>P<><50>SVW<56>0@1E<31>3<F8>P<C5>E<8D>d<F0><00>e<89><65>E<C7>h@<00>*<2A><><FF><FF><FF><04><>tU<74>E-@Ph@<00>P<E8><50><FF><FF><FF><08><>t;<3B>@$<24><><1F>Ѓ<F7><01>E<C7><45><FC><FE><FF><FF>M<8B>d<F0> Y_^[<5B><>]ËE<C38B><45><08>3<>=<00><0F>‹<94>Ëe<C38B><65>E<C7><45><FC><FE><FF>3<FF><33>M<8B>d<F0> Y_^[<5B><>]<5D><><C3>%<25> @<00>%<25> @<00><>he@d<>5<00>D$<10>l$<10>l$+<2B>SVW<56>0@1E<31>3<FC>P<C5>e<89><65>u<FF><75>E<8B><45>E<C7><45><FC><FE><FF><FF>E<89><45>E<8D>d<F0>ËM<C38B>d<F0> Y__^[<5B><>]QË<51>U<FF><55><8B>u<14>u<10>u <0C>uh<>@h0@<00><><00><><>Vhh3<>V<F6><56><00><> <0C><>t VVVVV<56><56><00><>^<5E>3<C3>Ë<C0>U<FF><55><8B><EC><10>0@<00>e<83><00>e<83>SW<53>N<BF>@<40><><00><>;<3B>t <0A><>t <09>У0@<00>`V<>E<8D>P<F8>< @<00>u<8B>3u<33><75> @3<><33> @3<><33> @3<><33>E<8D>P<F0> @<00>E<8B>3E<33>3<F0>;<3B>u<07>O<BE>@<40><> <0B><>u<07><><8B><C6> <0B><>50@<00>։50@^_[<5B><><C9>%t @<00>%x @<00>%| @<00>%<25> @<00>%<25> @<00>%<25> @<00>%<25> @<00>%<25> @<00>%<25> @Pd<50>5<00>D$ +d$ SVW<56>(<28><><8B>0@3<>P<C5>E<89><45>u<FF><75>E<C7><45><FC><FF><FF><FF>E<8D>d<F4>ËM<C38B>d<F4> Y__^[<5B><>]QËM<C38B>3<F0><33><CD><E8><AF><F7><FF><FF><E9><DD><FF><FF>M<8D><4D>%T @<00>T$<08>B <0C>J<8B>3<CC><33><C8><E8><90><F7><FF>J<8B>3<FC><33><C8><E8><86><F7><FF>l"@<00>s<E9><73><FF><00>#<00>#<00>#<00>)r)b)H)4))<00>(<00>(<00>(<00>(<00>(<00>(<00>)<00>#<00>$%<00>%&d&<00>&<00>$('<00>'<00>'<00>'<00>'(((6(<00>'H(Z(t(<00>('''<00>'<00>'l'^'R'F'>'>(0'<00>'<00>)<00>@W@<00>@<00>MoOl<00>!<00>@0@<00>0@bad allocationH0@<00>!@RSDSь<53><10><>J<>!<21><><F6>LZc:\users\seld\documents\visual studio 2010\Projects\hiddeninp\Release\hiddeninp.pdbe<00><00><><FE><FF><00><><D0><FF><00><><FE><FF>@@<00><><FE><FF><00><><CC><FF><00><><FE><FF>:@<00><><FE><FF><00><><D8><FF><00><><FE><FF><FF>@<00>@<00><><FF><FF><FF>@"<05>d"@<00>"<00># $#<00>&D H#(h <00>#<00>#<00>#<00>)r)b)H)4))<00>(<00>(<00>(<00>(<00>(<00>(<00>)<00>#<00>$%<00>%&d&<00>&<00>$('<00>'<00>'<00>'<00>'(((6(<00>'H(Z(t(<00>('''<00>'<00>'l'^'R'F'>'>(0'<00>'<00>)<00>GetConsoleMode<00>SetConsoleMode;GetStdHandleKERNEL32.dll??$?6DU?$char_traits@D@std@@V?$allocator@D@1@@std@@YAAAV?$basic_ostream@DU?$char_traits@D@std@@@0@AAV10@ABV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@0@@Z<00>?cout@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@AJ?cin@std@@3V?$basic_istream@DU?$char_traits@D@std@@@1@A<00>??$getline@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@YAAAV?$basic_istream@DU?$char_traits@D@std@@@0@AAV10@AAV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@0@@Z??6?$basic_ostream@DU?$char_traits@D@std@@@std@@QAEAAV01@P6AAAV01@AAV01@@Z@Z_??1?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@QAE@XZ{??0?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@QAE@XZ<00>?endl@std@@YAAAV?$basic_ostream@DU?$char_traits@D@std@@@1@AAV21@@ZMSVCP90.dll_amsg_exit<00>__getmainargs,_cexit|_exitf_XcptFilter<00>exit<00>__initenv_initterm_initterm_e<_configthreadlocale<00>__setusermatherr _adjust_fdiv<00>__p__commode<00>__p__fmodej_encode_pointer<00>__set_app_typeK_crt_debugger_hookC?terminate@@YAXXZMSVCR90.dll<00>_unlock<00>__dllonexitv_lock_onexit`_decode_pointers_except_handler4_common _invoke_watson?_controlfp_s<00>InterlockedExchange!Sleep<00>InterlockedCompareExchange-TerminateProcess<00>GetCurrentProcess>UnhandledExceptionFilterSetUnhandledExceptionFilter<00>IsDebuggerPresentTQueryPerformanceCounterfGetTickCount<00>GetCurrentThreadId<00>GetCurrentProcessIdOGetSystemTimeAsFileTimes__CxxFrameHandler3N<>@<40><><19>D<BF><44><FF><FF><FF><FF><FF><FF><FF><FF><FE><FF>$!@ <00>8<00>P<00>h<00> <00> <00><00>@(<00><00>CV<00>(4VS_VERSION_INFO<00><04><><00>StringFileInfob040904b0<00>QFileDescriptionReads from stdin without leaking info to the terminal and outputs back to stdout6 FileVersion1, 0, 0, 08 InternalNamehiddeninputPLegalCopyrightJordi Boggiano - 2012HOriginalFilenamehiddeninput.exe: ProductNameHidden Input: ProductVersion1, 0, 0, 0DVarFileInfo$Translation <04><assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
<security>
<requestedPrivileges>
<requestedExecutionLevel level="asInvoker" uiAccess="false"></requestedExecutionLevel>
</requestedPrivileges>
</security>
</trustInfo>
<dependency>
<dependentAssembly>
<assemblyIdentity type="win32" name="Microsoft.VC90.CRT" version="9.0.21022.8" processorArchitecture="x86" publicKeyToken="1fc8b3b9a1e18e3b"></assemblyIdentity>
</dependentAssembly>
</dependency>
</assembly>PAPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDING@00!0/080F0L0T0^0d0n0{0<>0<89>0<97>0<A1>0<A8>0<AE>0<B3>0<B8>0<BD>0<C2>0<C8>0<D0>0<E4>01#1-1@1J1O1T1v1{1<>1<84>1<89>1<96>1<A7>1<AD>1<B4>1<C8>1<CD>1<D3>1<DB>1<E1>1<E7>1<F4>12"2*23292A2M2_2j2p2<70>2<B9>2<BF>2<C7>2<CE>2<D3>2<D9>2<DF>2<E7>2<ED>2<F4>2 333%303N3T3Z3`3f3l3s3z3<7A>3<81>3<88>3<8F>3<96>3<9D>3<A5>3<AD>3<B5>3<C1>3<CA>3<CF>3<D5>3<DF>3<E8>3<F3>34444%4;4B4<42>4<8B>4<91>4<9A>4<A1>4<AC>4<B2>4<C6>4<DB>4<E6>45!5^5c5<63>5<84>5<89>5H6M6_6}6<>6<91>677 7*7w7|7<>7<C1>7<E4>7<F1>78 88=8E8P8V8\8b8h8n8t8z8<7A>8<80>8<9C>89 $<00>0<DC>0<E8>01 1t1x12 2@2\2`2h2t20 0<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console;
class Terminal
{
private static $width;
private static $height;
private static $stty;
/**
* Gets the terminal width.
*
* @return int
*/
public function getWidth()
{
$width = getenv('COLUMNS');
if (false !== $width) {
return (int) trim($width);
}
if (null === self::$width) {
self::initDimensions();
}
return self::$width ?: 80;
}
/**
* Gets the terminal height.
*
* @return int
*/
public function getHeight()
{
$height = getenv('LINES');
if (false !== $height) {
return (int) trim($height);
}
if (null === self::$height) {
self::initDimensions();
}
return self::$height ?: 50;
}
/**
* @internal
*
* @return bool
*/
public static function hasSttyAvailable()
{
if (null !== self::$stty) {
return self::$stty;
}
// skip check if exec function is disabled
if (!\function_exists('exec')) {
return false;
}
exec('stty 2>&1', $output, $exitcode);
return self::$stty = 0 === $exitcode;
}
private static function initDimensions()
{
if ('\\' === \DIRECTORY_SEPARATOR) {
if (preg_match('/^(\d+)x(\d+)(?: \((\d+)x(\d+)\))?$/', trim(getenv('ANSICON')), $matches)) {
// extract [w, H] from "wxh (WxH)"
// or [w, h] from "wxh"
self::$width = (int) $matches[1];
self::$height = isset($matches[4]) ? (int) $matches[4] : (int) $matches[2];
} elseif (!self::hasVt100Support() && self::hasSttyAvailable()) {
// only use stty on Windows if the terminal does not support vt100 (e.g. Windows 7 + git-bash)
// testing for stty in a Windows 10 vt100-enabled console will implicitly disable vt100 support on STDOUT
self::initDimensionsUsingStty();
} elseif (null !== $dimensions = self::getConsoleMode()) {
// extract [w, h] from "wxh"
self::$width = (int) $dimensions[0];
self::$height = (int) $dimensions[1];
}
} else {
self::initDimensionsUsingStty();
}
}
/**
* Returns whether STDOUT has vt100 support (some Windows 10+ configurations).
*/
private static function hasVt100Support(): bool
{
return \function_exists('sapi_windows_vt100_support') && sapi_windows_vt100_support(fopen('php://stdout', 'w'));
}
/**
* Initializes dimensions using the output of an stty columns line.
*/
private static function initDimensionsUsingStty()
{
if ($sttyString = self::getSttyColumns()) {
if (preg_match('/rows.(\d+);.columns.(\d+);/i', $sttyString, $matches)) {
// extract [w, h] from "rows h; columns w;"
self::$width = (int) $matches[2];
self::$height = (int) $matches[1];
} elseif (preg_match('/;.(\d+).rows;.(\d+).columns/i', $sttyString, $matches)) {
// extract [w, h] from "; h rows; w columns"
self::$width = (int) $matches[2];
self::$height = (int) $matches[1];
}
}
}
/**
* Runs and parses mode CON if it's available, suppressing any error output.
*
* @return int[]|null An array composed of the width and the height or null if it could not be parsed
*/
private static function getConsoleMode(): ?array
{
$info = self::readFromProcess('mode CON');
if (null === $info || !preg_match('/--------+\r?\n.+?(\d+)\r?\n.+?(\d+)\r?\n/', $info, $matches)) {
return null;
}
return [(int) $matches[2], (int) $matches[1]];
}
/**
* Runs and parses stty -a if it's available, suppressing any error output.
*/
private static function getSttyColumns(): ?string
{
return self::readFromProcess('stty -a | grep columns');
}
private static function readFromProcess(string $command): ?string
{
if (!\function_exists('proc_open')) {
return null;
}
$descriptorspec = [
1 => ['pipe', 'w'],
2 => ['pipe', 'w'],
];
$process = proc_open($command, $descriptorspec, $pipes, null, null, ['suppress_errors' => true]);
if (!\is_resource($process)) {
return null;
}
$info = stream_get_contents($pipes[1]);
fclose($pipes[1]);
fclose($pipes[2]);
proc_close($process);
return $info;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console;
use Symfony\Component\Console\Event\ConsoleCommandEvent;
use Symfony\Component\Console\Event\ConsoleErrorEvent;
use Symfony\Component\Console\Event\ConsoleSignalEvent;
use Symfony\Component\Console\Event\ConsoleTerminateEvent;
/**
* Contains all events dispatched by an Application.
*
* @author Francesco Levorato <git@flevour.net>
*/
final class ConsoleEvents
{
/**
* The COMMAND event allows you to attach listeners before any command is
* executed by the console. It also allows you to modify the command, input and output
* before they are handled to the command.
*
* @Event("Symfony\Component\Console\Event\ConsoleCommandEvent")
*/
const COMMAND = 'console.command';
/**
* The SIGNAL event allows you to perform some actions
* after the command execution was interrupted.
*
* @Event("Symfony\Component\Console\Event\ConsoleSignalEvent")
*/
const SIGNAL = 'console.signal';
/**
* The TERMINATE event allows you to attach listeners after a command is
* executed by the console.
*
* @Event("Symfony\Component\Console\Event\ConsoleTerminateEvent")
*/
const TERMINATE = 'console.terminate';
/**
* The ERROR event occurs when an uncaught exception or error appears.
*
* This event allows you to deal with the exception/error or
* to modify the thrown exception.
*
* @Event("Symfony\Component\Console\Event\ConsoleErrorEvent")
*/
const ERROR = 'console.error';
/**
* Event aliases.
*
* These aliases can be consumed by RegisterListenersPass.
*/
const ALIASES = [
ConsoleCommandEvent::class => self::COMMAND,
ConsoleErrorEvent::class => self::ERROR,
ConsoleSignalEvent::class => self::SIGNAL,
ConsoleTerminateEvent::class => self::TERMINATE,
];
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console;
use Symfony\Component\Console\Output\OutputInterface;
/**
* @author Pierre du Plessis <pdples@gmail.com>
*/
final class Cursor
{
private $output;
private $input;
public function __construct(OutputInterface $output, $input = null)
{
$this->output = $output;
$this->input = $input ?? (\defined('STDIN') ? \STDIN : fopen('php://input', 'r+'));
}
public function moveUp(int $lines = 1): self
{
$this->output->write(sprintf("\x1b[%dA", $lines));
return $this;
}
public function moveDown(int $lines = 1): self
{
$this->output->write(sprintf("\x1b[%dB", $lines));
return $this;
}
public function moveRight(int $columns = 1): self
{
$this->output->write(sprintf("\x1b[%dC", $columns));
return $this;
}
public function moveLeft(int $columns = 1): self
{
$this->output->write(sprintf("\x1b[%dD", $columns));
return $this;
}
public function moveToColumn(int $column): self
{
$this->output->write(sprintf("\x1b[%dG", $column));
return $this;
}
public function moveToPosition(int $column, int $row): self
{
$this->output->write(sprintf("\x1b[%d;%dH", $row + 1, $column));
return $this;
}
public function savePosition(): self
{
$this->output->write("\x1b7");
return $this;
}
public function restorePosition(): self
{
$this->output->write("\x1b8");
return $this;
}
public function hide(): self
{
$this->output->write("\x1b[?25l");
return $this;
}
public function show(): self
{
$this->output->write("\x1b[?25h\x1b[?0c");
return $this;
}
/**
* Clears all the output from the current line.
*/
public function clearLine(): self
{
$this->output->write("\x1b[2K");
return $this;
}
/**
* Clears all the output from the current line after the current position.
*/
public function clearLineAfter(): self
{
$this->output->write("\x1b[K");
return $this;
}
/**
* Clears all the output from the cursors' current position to the end of the screen.
*/
public function clearOutput(): self
{
$this->output->write("\x1b[0J");
return $this;
}
/**
* Clears the entire screen.
*/
public function clearScreen(): self
{
$this->output->write("\x1b[2J");
return $this;
}
/**
* Returns the current cursor position as x,y coordinates.
*/
public function getCurrentPosition(): array
{
static $isTtySupported;
if (null === $isTtySupported && \function_exists('proc_open')) {
$isTtySupported = (bool) @proc_open('echo 1 >/dev/null', [['file', '/dev/tty', 'r'], ['file', '/dev/tty', 'w'], ['file', '/dev/tty', 'w']], $pipes);
}
if (!$isTtySupported) {
return [1, 1];
}
$sttyMode = shell_exec('stty -g');
shell_exec('stty -icanon -echo');
@fwrite($this->input, "\033[6n");
$code = trim(fread($this->input, 1024));
shell_exec(sprintf('stty %s', $sttyMode));
sscanf($code, "\033[%d;%dR", $row, $col);
return [$col, $row];
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Formatter;
/**
* Formatter interface for console output that supports word wrapping.
*
* @author Roland Franssen <franssen.roland@gmail.com>
*/
interface WrappableOutputFormatterInterface extends OutputFormatterInterface
{
/**
* Formats a message according to the given styles, wrapping at `$width` (0 means no wrapping).
*/
public function formatAndWrap(?string $message, int $width);
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Formatter;
use Symfony\Component\Console\Color;
/**
* Formatter style class for defining styles.
*
* @author Konstantin Kudryashov <ever.zet@gmail.com>
*/
class OutputFormatterStyle implements OutputFormatterStyleInterface
{
private $color;
private $foreground;
private $background;
private $options;
private $href;
private $handlesHrefGracefully;
/**
* Initializes output formatter style.
*
* @param string|null $foreground The style foreground color name
* @param string|null $background The style background color name
*/
public function __construct(string $foreground = null, string $background = null, array $options = [])
{
$this->color = new Color($this->foreground = $foreground ?: '', $this->background = $background ?: '', $this->options = $options);
}
/**
* {@inheritdoc}
*/
public function setForeground(string $color = null)
{
$this->color = new Color($this->foreground = $color ?: '', $this->background, $this->options);
}
/**
* {@inheritdoc}
*/
public function setBackground(string $color = null)
{
$this->color = new Color($this->foreground, $this->background = $color ?: '', $this->options);
}
public function setHref(string $url): void
{
$this->href = $url;
}
/**
* {@inheritdoc}
*/
public function setOption(string $option)
{
$this->options[] = $option;
$this->color = new Color($this->foreground, $this->background, $this->options);
}
/**
* {@inheritdoc}
*/
public function unsetOption(string $option)
{
$pos = array_search($option, $this->options);
if (false !== $pos) {
unset($this->options[$pos]);
}
$this->color = new Color($this->foreground, $this->background, $this->options);
}
/**
* {@inheritdoc}
*/
public function setOptions(array $options)
{
$this->color = new Color($this->foreground, $this->background, $this->options = $options);
}
/**
* {@inheritdoc}
*/
public function apply(string $text)
{
if (null === $this->handlesHrefGracefully) {
$this->handlesHrefGracefully = 'JetBrains-JediTerm' !== getenv('TERMINAL_EMULATOR') && !getenv('KONSOLE_VERSION');
}
if (null !== $this->href && $this->handlesHrefGracefully) {
$text = "\033]8;;$this->href\033\\$text\033]8;;\033\\";
}
return $this->color->apply($text);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Formatter;
/**
* @author Tien Xuan Vo <tien.xuan.vo@gmail.com>
*/
final class NullOutputFormatter implements OutputFormatterInterface
{
private $style;
/**
* {@inheritdoc}
*/
public function format(?string $message): void
{
// do nothing
}
/**
* {@inheritdoc}
*/
public function getStyle(string $name): OutputFormatterStyleInterface
{
if ($this->style) {
return $this->style;
}
// to comply with the interface we must return a OutputFormatterStyleInterface
return $this->style = new NullOutputFormatterStyle();
}
/**
* {@inheritdoc}
*/
public function hasStyle(string $name): bool
{
return false;
}
/**
* {@inheritdoc}
*/
public function isDecorated(): bool
{
return false;
}
/**
* {@inheritdoc}
*/
public function setDecorated(bool $decorated): void
{
// do nothing
}
/**
* {@inheritdoc}
*/
public function setStyle(string $name, OutputFormatterStyleInterface $style): void
{
// do nothing
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Formatter;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Contracts\Service\ResetInterface;
/**
* @author Jean-François Simon <contact@jfsimon.fr>
*/
class OutputFormatterStyleStack implements ResetInterface
{
/**
* @var OutputFormatterStyleInterface[]
*/
private $styles;
private $emptyStyle;
public function __construct(OutputFormatterStyleInterface $emptyStyle = null)
{
$this->emptyStyle = $emptyStyle ?: new OutputFormatterStyle();
$this->reset();
}
/**
* Resets stack (ie. empty internal arrays).
*/
public function reset()
{
$this->styles = [];
}
/**
* Pushes a style in the stack.
*/
public function push(OutputFormatterStyleInterface $style)
{
$this->styles[] = $style;
}
/**
* Pops a style from the stack.
*
* @return OutputFormatterStyleInterface
*
* @throws InvalidArgumentException When style tags incorrectly nested
*/
public function pop(OutputFormatterStyleInterface $style = null)
{
if (empty($this->styles)) {
return $this->emptyStyle;
}
if (null === $style) {
return array_pop($this->styles);
}
foreach (array_reverse($this->styles, true) as $index => $stackedStyle) {
if ($style->apply('') === $stackedStyle->apply('')) {
$this->styles = \array_slice($this->styles, 0, $index);
return $stackedStyle;
}
}
throw new InvalidArgumentException('Incorrectly nested style tag found.');
}
/**
* Computes current style with stacks top codes.
*
* @return OutputFormatterStyle
*/
public function getCurrent()
{
if (empty($this->styles)) {
return $this->emptyStyle;
}
return $this->styles[\count($this->styles) - 1];
}
/**
* @return $this
*/
public function setEmptyStyle(OutputFormatterStyleInterface $emptyStyle)
{
$this->emptyStyle = $emptyStyle;
return $this;
}
/**
* @return OutputFormatterStyleInterface
*/
public function getEmptyStyle()
{
return $this->emptyStyle;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Formatter;
use Symfony\Component\Console\Exception\InvalidArgumentException;
/**
* Formatter class for console output.
*
* @author Konstantin Kudryashov <ever.zet@gmail.com>
* @author Roland Franssen <franssen.roland@gmail.com>
*/
class OutputFormatter implements WrappableOutputFormatterInterface
{
private $decorated;
private $styles = [];
private $styleStack;
public function __clone()
{
$this->styleStack = clone $this->styleStack;
foreach ($this->styles as $key => $value) {
$this->styles[$key] = clone $value;
}
}
/**
* Escapes "<" special char in given text.
*
* @return string Escaped text
*/
public static function escape(string $text)
{
$text = preg_replace('/([^\\\\]?)</', '$1\\<', $text);
return self::escapeTrailingBackslash($text);
}
/**
* Escapes trailing "\" in given text.
*
* @internal
*/
public static function escapeTrailingBackslash(string $text): string
{
if ('\\' === substr($text, -1)) {
$len = \strlen($text);
$text = rtrim($text, '\\');
$text = str_replace("\0", '', $text);
$text .= str_repeat("\0", $len - \strlen($text));
}
return $text;
}
/**
* Initializes console output formatter.
*
* @param OutputFormatterStyleInterface[] $styles Array of "name => FormatterStyle" instances
*/
public function __construct(bool $decorated = false, array $styles = [])
{
$this->decorated = $decorated;
$this->setStyle('error', new OutputFormatterStyle('white', 'red'));
$this->setStyle('info', new OutputFormatterStyle('green'));
$this->setStyle('comment', new OutputFormatterStyle('yellow'));
$this->setStyle('question', new OutputFormatterStyle('black', 'cyan'));
foreach ($styles as $name => $style) {
$this->setStyle($name, $style);
}
$this->styleStack = new OutputFormatterStyleStack();
}
/**
* {@inheritdoc}
*/
public function setDecorated(bool $decorated)
{
$this->decorated = $decorated;
}
/**
* {@inheritdoc}
*/
public function isDecorated()
{
return $this->decorated;
}
/**
* {@inheritdoc}
*/
public function setStyle(string $name, OutputFormatterStyleInterface $style)
{
$this->styles[strtolower($name)] = $style;
}
/**
* {@inheritdoc}
*/
public function hasStyle(string $name)
{
return isset($this->styles[strtolower($name)]);
}
/**
* {@inheritdoc}
*/
public function getStyle(string $name)
{
if (!$this->hasStyle($name)) {
throw new InvalidArgumentException(sprintf('Undefined style: "%s".', $name));
}
return $this->styles[strtolower($name)];
}
/**
* {@inheritdoc}
*/
public function format(?string $message)
{
return $this->formatAndWrap($message, 0);
}
/**
* {@inheritdoc}
*/
public function formatAndWrap(?string $message, int $width)
{
$offset = 0;
$output = '';
$tagRegex = '[a-z][^<>]*+';
$currentLineLength = 0;
preg_match_all("#<(($tagRegex) | /($tagRegex)?)>#ix", $message, $matches, \PREG_OFFSET_CAPTURE);
foreach ($matches[0] as $i => $match) {
$pos = $match[1];
$text = $match[0];
if (0 != $pos && '\\' == $message[$pos - 1]) {
continue;
}
// add the text up to the next tag
$output .= $this->applyCurrentStyle(substr($message, $offset, $pos - $offset), $output, $width, $currentLineLength);
$offset = $pos + \strlen($text);
// opening tag?
if ($open = '/' != $text[1]) {
$tag = $matches[1][$i][0];
} else {
$tag = isset($matches[3][$i][0]) ? $matches[3][$i][0] : '';
}
if (!$open && !$tag) {
// </>
$this->styleStack->pop();
} elseif (null === $style = $this->createStyleFromString($tag)) {
$output .= $this->applyCurrentStyle($text, $output, $width, $currentLineLength);
} elseif ($open) {
$this->styleStack->push($style);
} else {
$this->styleStack->pop($style);
}
}
$output .= $this->applyCurrentStyle(substr($message, $offset), $output, $width, $currentLineLength);
if (false !== strpos($output, "\0")) {
return strtr($output, ["\0" => '\\', '\\<' => '<']);
}
return str_replace('\\<', '<', $output);
}
/**
* @return OutputFormatterStyleStack
*/
public function getStyleStack()
{
return $this->styleStack;
}
/**
* Tries to create new style instance from string.
*/
private function createStyleFromString(string $string): ?OutputFormatterStyleInterface
{
if (isset($this->styles[$string])) {
return $this->styles[$string];
}
if (!preg_match_all('/([^=]+)=([^;]+)(;|$)/', $string, $matches, \PREG_SET_ORDER)) {
return null;
}
$style = new OutputFormatterStyle();
foreach ($matches as $match) {
array_shift($match);
$match[0] = strtolower($match[0]);
if ('fg' == $match[0]) {
$style->setForeground(strtolower($match[1]));
} elseif ('bg' == $match[0]) {
$style->setBackground(strtolower($match[1]));
} elseif ('href' === $match[0]) {
$style->setHref($match[1]);
} elseif ('options' === $match[0]) {
preg_match_all('([^,;]+)', strtolower($match[1]), $options);
$options = array_shift($options);
foreach ($options as $option) {
$style->setOption($option);
}
} else {
return null;
}
}
return $style;
}
/**
* Applies current style from stack to text, if must be applied.
*/
private function applyCurrentStyle(string $text, string $current, int $width, int &$currentLineLength): string
{
if ('' === $text) {
return '';
}
if (!$width) {
return $this->isDecorated() ? $this->styleStack->getCurrent()->apply($text) : $text;
}
if (!$currentLineLength && '' !== $current) {
$text = ltrim($text);
}
if ($currentLineLength) {
$prefix = substr($text, 0, $i = $width - $currentLineLength)."\n";
$text = substr($text, $i);
} else {
$prefix = '';
}
preg_match('~(\\n)$~', $text, $matches);
$text = $prefix.preg_replace('~([^\\n]{'.$width.'})\\ *~', "\$1\n", $text);
$text = rtrim($text, "\n").($matches[1] ?? '');
if (!$currentLineLength && '' !== $current && "\n" !== substr($current, -1)) {
$text = "\n".$text;
}
$lines = explode("\n", $text);
foreach ($lines as $line) {
$currentLineLength += \strlen($line);
if ($width <= $currentLineLength) {
$currentLineLength = 0;
}
}
if ($this->isDecorated()) {
foreach ($lines as $i => $line) {
$lines[$i] = $this->styleStack->getCurrent()->apply($line);
}
}
return implode("\n", $lines);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Formatter;
/**
* Formatter interface for console output.
*
* @author Konstantin Kudryashov <ever.zet@gmail.com>
*/
interface OutputFormatterInterface
{
/**
* Sets the decorated flag.
*/
public function setDecorated(bool $decorated);
/**
* Gets the decorated flag.
*
* @return bool true if the output will decorate messages, false otherwise
*/
public function isDecorated();
/**
* Sets a new style.
*/
public function setStyle(string $name, OutputFormatterStyleInterface $style);
/**
* Checks if output formatter has style with specified name.
*
* @return bool
*/
public function hasStyle(string $name);
/**
* Gets style options from style with specified name.
*
* @return OutputFormatterStyleInterface
*
* @throws \InvalidArgumentException When style isn't defined
*/
public function getStyle(string $name);
/**
* Formats a message according to the given styles.
*/
public function format(?string $message);
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Formatter;
/**
* @author Tien Xuan Vo <tien.xuan.vo@gmail.com>
*/
final class NullOutputFormatterStyle implements OutputFormatterStyleInterface
{
/**
* {@inheritdoc}
*/
public function apply(string $text): string
{
return $text;
}
/**
* {@inheritdoc}
*/
public function setBackground(string $color = null): void
{
// do nothing
}
/**
* {@inheritdoc}
*/
public function setForeground(string $color = null): void
{
// do nothing
}
/**
* {@inheritdoc}
*/
public function setOption(string $option): void
{
// do nothing
}
/**
* {@inheritdoc}
*/
public function setOptions(array $options): void
{
// do nothing
}
/**
* {@inheritdoc}
*/
public function unsetOption(string $option): void
{
// do nothing
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Formatter;
/**
* Formatter style interface for defining styles.
*
* @author Konstantin Kudryashov <ever.zet@gmail.com>
*/
interface OutputFormatterStyleInterface
{
/**
* Sets style foreground color.
*/
public function setForeground(string $color = null);
/**
* Sets style background color.
*/
public function setBackground(string $color = null);
/**
* Sets some specific style option.
*/
public function setOption(string $option);
/**
* Unsets some specific style option.
*/
public function unsetOption(string $option);
/**
* Sets multiple style options at once.
*/
public function setOptions(array $options);
/**
* Applies the style to a given text.
*
* @return string
*/
public function apply(string $text);
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console;
use Symfony\Component\Console\Exception\InvalidArgumentException;
/**
* @author Fabien Potencier <fabien@symfony.com>
*/
final class Color
{
private const COLORS = [
'black' => 0,
'red' => 1,
'green' => 2,
'yellow' => 3,
'blue' => 4,
'magenta' => 5,
'cyan' => 6,
'white' => 7,
'default' => 9,
];
private const AVAILABLE_OPTIONS = [
'bold' => ['set' => 1, 'unset' => 22],
'underscore' => ['set' => 4, 'unset' => 24],
'blink' => ['set' => 5, 'unset' => 25],
'reverse' => ['set' => 7, 'unset' => 27],
'conceal' => ['set' => 8, 'unset' => 28],
];
private $foreground;
private $background;
private $options = [];
public function __construct(string $foreground = '', string $background = '', array $options = [])
{
$this->foreground = $this->parseColor($foreground);
$this->background = $this->parseColor($background);
foreach ($options as $option) {
if (!isset(self::AVAILABLE_OPTIONS[$option])) {
throw new InvalidArgumentException(sprintf('Invalid option specified: "%s". Expected one of (%s).', $option, implode(', ', array_keys(self::AVAILABLE_OPTIONS))));
}
$this->options[$option] = self::AVAILABLE_OPTIONS[$option];
}
}
public function apply(string $text): string
{
return $this->set().$text.$this->unset();
}
public function set(): string
{
$setCodes = [];
if ('' !== $this->foreground) {
$setCodes[] = '3'.$this->foreground;
}
if ('' !== $this->background) {
$setCodes[] = '4'.$this->background;
}
foreach ($this->options as $option) {
$setCodes[] = $option['set'];
}
if (0 === \count($setCodes)) {
return '';
}
return sprintf("\033[%sm", implode(';', $setCodes));
}
public function unset(): string
{
$unsetCodes = [];
if ('' !== $this->foreground) {
$unsetCodes[] = 39;
}
if ('' !== $this->background) {
$unsetCodes[] = 49;
}
foreach ($this->options as $option) {
$unsetCodes[] = $option['unset'];
}
if (0 === \count($unsetCodes)) {
return '';
}
return sprintf("\033[%sm", implode(';', $unsetCodes));
}
private function parseColor(string $color): string
{
if ('' === $color) {
return '';
}
if ('#' === $color[0]) {
$color = substr($color, 1);
if (3 === \strlen($color)) {
$color = $color[0].$color[0].$color[1].$color[1].$color[2].$color[2];
}
if (6 !== \strlen($color)) {
throw new InvalidArgumentException(sprintf('Invalid "%s" color.', $color));
}
return $this->convertHexColorToAnsi(hexdec($color));
}
if (!isset(self::COLORS[$color])) {
throw new InvalidArgumentException(sprintf('Invalid "%s" color; expected one of (%s).', $color, implode(', ', array_keys(self::COLORS))));
}
return (string) self::COLORS[$color];
}
private function convertHexColorToAnsi(int $color): string
{
$r = ($color >> 16) & 255;
$g = ($color >> 8) & 255;
$b = $color & 255;
// see https://github.com/termstandard/colors/ for more information about true color support
if ('truecolor' !== getenv('COLORTERM')) {
return (string) $this->degradeHexColorToAnsi($r, $g, $b);
}
return sprintf('8;2;%d;%d;%d', $r, $g, $b);
}
private function degradeHexColorToAnsi(int $r, int $g, int $b): int
{
if (0 === round($this->getSaturation($r, $g, $b) / 50)) {
return 0;
}
return (round($b / 255) << 2) | (round($g / 255) << 1) | round($r / 255);
}
private function getSaturation(int $r, int $g, int $b): int
{
$r = $r / 255;
$g = $g / 255;
$b = $b / 255;
$v = max($r, $g, $b);
if (0 === $diff = $v - min($r, $g, $b)) {
return 0;
}
return (int) $diff * 100 / $v;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\DependencyInjection;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\CommandLoader\ContainerCommandLoader;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\TypedReference;
/**
* Registers console commands.
*
* @author Grégoire Pineau <lyrixx@lyrixx.info>
*/
class AddConsoleCommandPass implements CompilerPassInterface
{
private $commandLoaderServiceId;
private $commandTag;
private $noPreloadTag;
public function __construct(string $commandLoaderServiceId = 'console.command_loader', string $commandTag = 'console.command', string $noPreloadTag = 'container.no_preload')
{
$this->commandLoaderServiceId = $commandLoaderServiceId;
$this->commandTag = $commandTag;
$this->noPreloadTag = $noPreloadTag;
}
public function process(ContainerBuilder $container)
{
$commandServices = $container->findTaggedServiceIds($this->commandTag, true);
$lazyCommandMap = [];
$lazyCommandRefs = [];
$serviceIds = [];
foreach ($commandServices as $id => $tags) {
$definition = $container->getDefinition($id);
$definition->addTag($this->noPreloadTag);
$class = $container->getParameterBag()->resolveValue($definition->getClass());
if (isset($tags[0]['command'])) {
$commandName = $tags[0]['command'];
} else {
if (!$r = $container->getReflectionClass($class)) {
throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id));
}
if (!$r->isSubclassOf(Command::class)) {
throw new InvalidArgumentException(sprintf('The service "%s" tagged "%s" must be a subclass of "%s".', $id, $this->commandTag, Command::class));
}
$commandName = $class::getDefaultName();
}
if (null === $commandName) {
if (!$definition->isPublic() || $definition->isPrivate()) {
$commandId = 'console.command.public_alias.'.$id;
$container->setAlias($commandId, $id)->setPublic(true);
$id = $commandId;
}
$serviceIds[] = $id;
continue;
}
unset($tags[0]);
$lazyCommandMap[$commandName] = $id;
$lazyCommandRefs[$id] = new TypedReference($id, $class);
$aliases = [];
foreach ($tags as $tag) {
if (isset($tag['command'])) {
$aliases[] = $tag['command'];
$lazyCommandMap[$tag['command']] = $id;
}
}
$definition->addMethodCall('setName', [$commandName]);
if ($aliases) {
$definition->addMethodCall('setAliases', [$aliases]);
}
}
$container
->register($this->commandLoaderServiceId, ContainerCommandLoader::class)
->setPublic(true)
->addTag($this->noPreloadTag)
->setArguments([ServiceLocatorTagPass::register($container, $lazyCommandRefs), $lazyCommandMap]);
$container->setParameter('console.command.ids', $serviceIds);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Tester;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Output\StreamOutput;
/**
* @author Amrouche Hamza <hamza.simperfit@gmail.com>
*/
trait TesterTrait
{
/** @var StreamOutput */
private $output;
private $inputs = [];
private $captureStreamsIndependently = false;
/**
* Gets the display returned by the last execution of the command or application.
*
* @throws \RuntimeException If it's called before the execute method
*
* @return string The display
*/
public function getDisplay(bool $normalize = false)
{
if (null === $this->output) {
throw new \RuntimeException('Output not initialized, did you execute the command before requesting the display?');
}
rewind($this->output->getStream());
$display = stream_get_contents($this->output->getStream());
if ($normalize) {
$display = str_replace(\PHP_EOL, "\n", $display);
}
return $display;
}
/**
* Gets the output written to STDERR by the application.
*
* @param bool $normalize Whether to normalize end of lines to \n or not
*
* @return string
*/
public function getErrorOutput(bool $normalize = false)
{
if (!$this->captureStreamsIndependently) {
throw new \LogicException('The error output is not available when the tester is run without "capture_stderr_separately" option set.');
}
rewind($this->output->getErrorOutput()->getStream());
$display = stream_get_contents($this->output->getErrorOutput()->getStream());
if ($normalize) {
$display = str_replace(\PHP_EOL, "\n", $display);
}
return $display;
}
/**
* Gets the input instance used by the last execution of the command or application.
*
* @return InputInterface The current input instance
*/
public function getInput()
{
return $this->input;
}
/**
* Gets the output instance used by the last execution of the command or application.
*
* @return OutputInterface The current output instance
*/
public function getOutput()
{
return $this->output;
}
/**
* Gets the status code returned by the last execution of the command or application.
*
* @throws \RuntimeException If it's called before the execute method
*
* @return int The status code
*/
public function getStatusCode()
{
if (null === $this->statusCode) {
throw new \RuntimeException('Status code not initialized, did you execute the command before requesting the status code?');
}
return $this->statusCode;
}
/**
* Sets the user inputs.
*
* @param array $inputs An array of strings representing each input
* passed to the command input stream
*
* @return $this
*/
public function setInputs(array $inputs)
{
$this->inputs = $inputs;
return $this;
}
/**
* Initializes the output property.
*
* Available options:
*
* * decorated: Sets the output decorated flag
* * verbosity: Sets the output verbosity flag
* * capture_stderr_separately: Make output of stdOut and stdErr separately available
*/
private function initOutput(array $options)
{
$this->captureStreamsIndependently = \array_key_exists('capture_stderr_separately', $options) && $options['capture_stderr_separately'];
if (!$this->captureStreamsIndependently) {
$this->output = new StreamOutput(fopen('php://memory', 'w', false));
if (isset($options['decorated'])) {
$this->output->setDecorated($options['decorated']);
}
if (isset($options['verbosity'])) {
$this->output->setVerbosity($options['verbosity']);
}
} else {
$this->output = new ConsoleOutput(
isset($options['verbosity']) ? $options['verbosity'] : ConsoleOutput::VERBOSITY_NORMAL,
isset($options['decorated']) ? $options['decorated'] : null
);
$errorOutput = new StreamOutput(fopen('php://memory', 'w', false));
$errorOutput->setFormatter($this->output->getFormatter());
$errorOutput->setVerbosity($this->output->getVerbosity());
$errorOutput->setDecorated($this->output->isDecorated());
$reflectedOutput = new \ReflectionObject($this->output);
$strErrProperty = $reflectedOutput->getProperty('stderr');
$strErrProperty->setAccessible(true);
$strErrProperty->setValue($this->output, $errorOutput);
$reflectedParent = $reflectedOutput->getParentClass();
$streamProperty = $reflectedParent->getProperty('stream');
$streamProperty->setAccessible(true);
$streamProperty->setValue($this->output, fopen('php://memory', 'w', false));
}
}
/**
* @return resource
*/
private static function createStream(array $inputs)
{
$stream = fopen('php://memory', 'r+', false);
foreach ($inputs as $input) {
fwrite($stream, $input.\PHP_EOL);
}
rewind($stream);
return $stream;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Tester;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Input\ArrayInput;
/**
* Eases the testing of console applications.
*
* When testing an application, don't forget to disable the auto exit flag:
*
* $application = new Application();
* $application->setAutoExit(false);
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class ApplicationTester
{
use TesterTrait;
private $application;
private $input;
private $statusCode;
public function __construct(Application $application)
{
$this->application = $application;
}
/**
* Executes the application.
*
* Available options:
*
* * interactive: Sets the input interactive flag
* * decorated: Sets the output decorated flag
* * verbosity: Sets the output verbosity flag
* * capture_stderr_separately: Make output of stdOut and stdErr separately available
*
* @return int The command exit code
*/
public function run(array $input, array $options = [])
{
$this->input = new ArrayInput($input);
if (isset($options['interactive'])) {
$this->input->setInteractive($options['interactive']);
}
if ($this->inputs) {
$this->input->setStream(self::createStream($this->inputs));
}
$this->initOutput($options);
return $this->statusCode = $this->application->run($this->input, $this->output);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Tester;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\ArrayInput;
/**
* Eases the testing of console commands.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Robin Chalas <robin.chalas@gmail.com>
*/
class CommandTester
{
use TesterTrait;
private $command;
private $input;
private $statusCode;
public function __construct(Command $command)
{
$this->command = $command;
}
/**
* Executes the command.
*
* Available execution options:
*
* * interactive: Sets the input interactive flag
* * decorated: Sets the output decorated flag
* * verbosity: Sets the output verbosity flag
* * capture_stderr_separately: Make output of stdOut and stdErr separately available
*
* @param array $input An array of command arguments and options
* @param array $options An array of execution options
*
* @return int The command exit code
*/
public function execute(array $input, array $options = [])
{
// set the command name automatically if the application requires
// this argument and no command name was passed
if (!isset($input['command'])
&& (null !== $application = $this->command->getApplication())
&& $application->getDefinition()->hasArgument('command')
) {
$input = array_merge(['command' => $this->command->getName()], $input);
}
$this->input = new ArrayInput($input);
// Use an in-memory input stream even if no inputs are set so that QuestionHelper::ask() does not rely on the blocking STDIN.
$this->input->setStream(self::createStream($this->inputs));
if (isset($options['interactive'])) {
$this->input->setInteractive($options['interactive']);
}
if (!isset($options['decorated'])) {
$options['decorated'] = false;
}
$this->initOutput($options);
return $this->statusCode = $this->command->run($this->input, $this->output);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Event;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Allows to handle throwables thrown while running a command.
*
* @author Wouter de Jong <wouter@wouterj.nl>
*/
final class ConsoleErrorEvent extends ConsoleEvent
{
private $error;
private $exitCode;
public function __construct(InputInterface $input, OutputInterface $output, \Throwable $error, Command $command = null)
{
parent::__construct($command, $input, $output);
$this->error = $error;
}
public function getError(): \Throwable
{
return $this->error;
}
public function setError(\Throwable $error): void
{
$this->error = $error;
}
public function setExitCode(int $exitCode): void
{
$this->exitCode = $exitCode;
$r = new \ReflectionProperty($this->error, 'code');
$r->setAccessible(true);
$r->setValue($this->error, $this->exitCode);
}
public function getExitCode(): int
{
return null !== $this->exitCode ? $this->exitCode : (\is_int($this->error->getCode()) && 0 !== $this->error->getCode() ? $this->error->getCode() : 1);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Event;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Allows to manipulate the exit code of a command after its execution.
*
* @author Francesco Levorato <git@flevour.net>
*/
final class ConsoleTerminateEvent extends ConsoleEvent
{
private $exitCode;
public function __construct(Command $command, InputInterface $input, OutputInterface $output, int $exitCode)
{
parent::__construct($command, $input, $output);
$this->setExitCode($exitCode);
}
public function setExitCode(int $exitCode): void
{
$this->exitCode = $exitCode;
}
public function getExitCode(): int
{
return $this->exitCode;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Event;
/**
* Allows to do things before the command is executed, like skipping the command or changing the input.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
final class ConsoleCommandEvent extends ConsoleEvent
{
/**
* The return code for skipped commands, this will also be passed into the terminate event.
*/
const RETURN_CODE_DISABLED = 113;
/**
* Indicates if the command should be run or skipped.
*/
private $commandShouldRun = true;
/**
* Disables the command, so it won't be run.
*/
public function disableCommand(): bool
{
return $this->commandShouldRun = false;
}
public function enableCommand(): bool
{
return $this->commandShouldRun = true;
}
/**
* Returns true if the command is runnable, false otherwise.
*/
public function commandShouldRun(): bool
{
return $this->commandShouldRun;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Event;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Contracts\EventDispatcher\Event;
/**
* Allows to inspect input and output of a command.
*
* @author Francesco Levorato <git@flevour.net>
*/
class ConsoleEvent extends Event
{
protected $command;
private $input;
private $output;
public function __construct(Command $command = null, InputInterface $input, OutputInterface $output)
{
$this->command = $command;
$this->input = $input;
$this->output = $output;
}
/**
* Gets the command that is executed.
*
* @return Command|null A Command instance
*/
public function getCommand()
{
return $this->command;
}
/**
* Gets the input instance.
*
* @return InputInterface An InputInterface instance
*/
public function getInput()
{
return $this->input;
}
/**
* Gets the output instance.
*
* @return OutputInterface An OutputInterface instance
*/
public function getOutput()
{
return $this->output;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Event;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* @author marie <marie@users.noreply.github.com>
*/
final class ConsoleSignalEvent extends ConsoleEvent
{
private $handlingSignal;
public function __construct(Command $command, InputInterface $input, OutputInterface $output, int $handlingSignal)
{
parent::__construct($command, $input, $output);
$this->handlingSignal = $handlingSignal;
}
public function getHandlingSignal(): int
{
return $this->handlingSignal;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Extension;
use Symfony\Component\Config\Definition\ConfigurationInterface;
use Symfony\Component\Config\Definition\Processor;
use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Exception\BadMethodCallException;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Exception\LogicException;
/**
* Provides useful features shared by many extensions.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
abstract class Extension implements ExtensionInterface, ConfigurationExtensionInterface
{
private $processedConfigs = [];
/**
* {@inheritdoc}
*/
public function getXsdValidationBasePath()
{
return false;
}
/**
* {@inheritdoc}
*/
public function getNamespace()
{
return 'http://example.org/schema/dic/'.$this->getAlias();
}
/**
* Returns the recommended alias to use in XML.
*
* This alias is also the mandatory prefix to use when using YAML.
*
* This convention is to remove the "Extension" postfix from the class
* name and then lowercase and underscore the result. So:
*
* AcmeHelloExtension
*
* becomes
*
* acme_hello
*
* This can be overridden in a sub-class to specify the alias manually.
*
* @return string The alias
*
* @throws BadMethodCallException When the extension name does not follow conventions
*/
public function getAlias()
{
$className = static::class;
if ('Extension' != substr($className, -9)) {
throw new BadMethodCallException('This extension does not follow the naming convention; you must overwrite the getAlias() method.');
}
$classBaseName = substr(strrchr($className, '\\'), 1, -9);
return Container::underscore($classBaseName);
}
/**
* {@inheritdoc}
*/
public function getConfiguration(array $config, ContainerBuilder $container)
{
$class = static::class;
if (false !== strpos($class, "\0")) {
return null; // ignore anonymous classes
}
$class = substr_replace($class, '\Configuration', strrpos($class, '\\'));
$class = $container->getReflectionClass($class);
if (!$class) {
return null;
}
if (!$class->implementsInterface(ConfigurationInterface::class)) {
throw new LogicException(sprintf('The extension configuration class "%s" must implement "%s".', $class->getName(), ConfigurationInterface::class));
}
if (!($constructor = $class->getConstructor()) || !$constructor->getNumberOfRequiredParameters()) {
return $class->newInstance();
}
return null;
}
final protected function processConfiguration(ConfigurationInterface $configuration, array $configs): array
{
$processor = new Processor();
return $this->processedConfigs[] = $processor->processConfiguration($configuration, $configs);
}
/**
* @internal
*/
final public function getProcessedConfigs(): array
{
try {
return $this->processedConfigs;
} finally {
$this->processedConfigs = [];
}
}
/**
* @return bool Whether the configuration is enabled
*
* @throws InvalidArgumentException When the config is not enableable
*/
protected function isConfigEnabled(ContainerBuilder $container, array $config)
{
if (!\array_key_exists('enabled', $config)) {
throw new InvalidArgumentException("The config array has no 'enabled' key.");
}
return (bool) $container->getParameterBag()->resolveValue($config['enabled']);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Extension;
use Symfony\Component\Config\Definition\ConfigurationInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
/**
* ConfigurationExtensionInterface is the interface implemented by container extension classes.
*
* @author Kevin Bond <kevinbond@gmail.com>
*/
interface ConfigurationExtensionInterface
{
/**
* Returns extension configuration.
*
* @return ConfigurationInterface|null The configuration or null
*/
public function getConfiguration(array $config, ContainerBuilder $container);
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Extension;
use Symfony\Component\DependencyInjection\ContainerBuilder;
interface PrependExtensionInterface
{
/**
* Allow an extension to prepend the extension configurations.
*/
public function prepend(ContainerBuilder $container);
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Extension;
use Symfony\Component\DependencyInjection\ContainerBuilder;
/**
* ExtensionInterface is the interface implemented by container extension classes.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
interface ExtensionInterface
{
/**
* Loads a specific configuration.
*
* @throws \InvalidArgumentException When provided tag is not defined in this extension
*/
public function load(array $configs, ContainerBuilder $container);
/**
* Returns the namespace to be used for this extension (XML namespace).
*
* @return string The XML namespace
*/
public function getNamespace();
/**
* Returns the base path for the XSD files.
*
* @return string|false
*/
public function getXsdValidationBasePath();
/**
* Returns the recommended alias to use in XML.
*
* This alias is also the mandatory prefix to use when using YAML.
*
* @return string The alias
*/
public function getAlias();
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection;
use Symfony\Component\DependencyInjection\Argument\RewindableGenerator;
use Symfony\Component\DependencyInjection\Argument\ServiceLocator as ArgumentServiceLocator;
use Symfony\Component\DependencyInjection\Exception\EnvNotFoundException;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Exception\ParameterCircularReferenceException;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException;
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
use Symfony\Component\DependencyInjection\ParameterBag\EnvPlaceholderParameterBag;
use Symfony\Component\DependencyInjection\ParameterBag\FrozenParameterBag;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Contracts\Service\ResetInterface;
// Help opcache.preload discover always-needed symbols
class_exists(RewindableGenerator::class);
class_exists(ArgumentServiceLocator::class);
/**
* Container is a dependency injection container.
*
* It gives access to object instances (services).
* Services and parameters are simple key/pair stores.
* The container can have four possible behaviors when a service
* does not exist (or is not initialized for the last case):
*
* * EXCEPTION_ON_INVALID_REFERENCE: Throws an exception (the default)
* * NULL_ON_INVALID_REFERENCE: Returns null
* * IGNORE_ON_INVALID_REFERENCE: Ignores the wrapping command asking for the reference
* (for instance, ignore a setter if the service does not exist)
* * IGNORE_ON_UNINITIALIZED_REFERENCE: Ignores/returns null for uninitialized services or invalid references
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class Container implements ContainerInterface, ResetInterface
{
protected $parameterBag;
protected $services = [];
protected $privates = [];
protected $fileMap = [];
protected $methodMap = [];
protected $factories = [];
protected $aliases = [];
protected $loading = [];
protected $resolving = [];
protected $syntheticIds = [];
private $envCache = [];
private $compiled = false;
private $getEnv;
public function __construct(ParameterBagInterface $parameterBag = null)
{
$this->parameterBag = $parameterBag ?: new EnvPlaceholderParameterBag();
}
/**
* Compiles the container.
*
* This method does two things:
*
* * Parameter values are resolved;
* * The parameter bag is frozen.
*/
public function compile()
{
$this->parameterBag->resolve();
$this->parameterBag = new FrozenParameterBag($this->parameterBag->all());
$this->compiled = true;
}
/**
* Returns true if the container is compiled.
*
* @return bool
*/
public function isCompiled()
{
return $this->compiled;
}
/**
* Gets the service container parameter bag.
*
* @return ParameterBagInterface A ParameterBagInterface instance
*/
public function getParameterBag()
{
return $this->parameterBag;
}
/**
* Gets a parameter.
*
* @param string $name The parameter name
*
* @return mixed The parameter value
*
* @throws InvalidArgumentException if the parameter is not defined
*/
public function getParameter(string $name)
{
return $this->parameterBag->get($name);
}
/**
* Checks if a parameter exists.
*
* @param string $name The parameter name
*
* @return bool The presence of parameter in container
*/
public function hasParameter(string $name)
{
return $this->parameterBag->has($name);
}
/**
* Sets a parameter.
*
* @param string $name The parameter name
* @param mixed $value The parameter value
*/
public function setParameter(string $name, $value)
{
$this->parameterBag->set($name, $value);
}
/**
* Sets a service.
*
* Setting a synthetic service to null resets it: has() returns false and get()
* behaves in the same way as if the service was never created.
*/
public function set(string $id, ?object $service)
{
// Runs the internal initializer; used by the dumped container to include always-needed files
if (isset($this->privates['service_container']) && $this->privates['service_container'] instanceof \Closure) {
$initialize = $this->privates['service_container'];
unset($this->privates['service_container']);
$initialize();
}
if ('service_container' === $id) {
throw new InvalidArgumentException('You cannot set service "service_container".');
}
if (!(isset($this->fileMap[$id]) || isset($this->methodMap[$id]))) {
if (isset($this->syntheticIds[$id]) || !isset($this->getRemovedIds()[$id])) {
// no-op
} elseif (null === $service) {
throw new InvalidArgumentException(sprintf('The "%s" service is private, you cannot unset it.', $id));
} else {
throw new InvalidArgumentException(sprintf('The "%s" service is private, you cannot replace it.', $id));
}
} elseif (isset($this->services[$id])) {
throw new InvalidArgumentException(sprintf('The "%s" service is already initialized, you cannot replace it.', $id));
}
if (isset($this->aliases[$id])) {
unset($this->aliases[$id]);
}
if (null === $service) {
unset($this->services[$id]);
return;
}
$this->services[$id] = $service;
}
/**
* Returns true if the given service is defined.
*
* @param string $id The service identifier
*
* @return bool true if the service is defined, false otherwise
*/
public function has($id)
{
if (isset($this->aliases[$id])) {
$id = $this->aliases[$id];
}
if (isset($this->services[$id])) {
return true;
}
if ('service_container' === $id) {
return true;
}
return isset($this->fileMap[$id]) || isset($this->methodMap[$id]);
}
/**
* Gets a service.
*
* @param string $id The service identifier
* @param int $invalidBehavior The behavior when the service does not exist
*
* @return object|null The associated service
*
* @throws ServiceCircularReferenceException When a circular reference is detected
* @throws ServiceNotFoundException When the service is not defined
* @throws \Exception if an exception has been thrown when the service has been resolved
*
* @see Reference
*/
public function get($id, int $invalidBehavior = /* self::EXCEPTION_ON_INVALID_REFERENCE */ 1)
{
return $this->services[$id]
?? $this->services[$id = $this->aliases[$id] ?? $id]
?? ('service_container' === $id ? $this : ($this->factories[$id] ?? [$this, 'make'])($id, $invalidBehavior));
}
/**
* Creates a service.
*
* As a separate method to allow "get()" to use the really fast `??` operator.
*/
private function make(string $id, int $invalidBehavior)
{
if (isset($this->loading[$id])) {
throw new ServiceCircularReferenceException($id, array_merge(array_keys($this->loading), [$id]));
}
$this->loading[$id] = true;
try {
if (isset($this->fileMap[$id])) {
return /* self::IGNORE_ON_UNINITIALIZED_REFERENCE */ 4 === $invalidBehavior ? null : $this->load($this->fileMap[$id]);
} elseif (isset($this->methodMap[$id])) {
return /* self::IGNORE_ON_UNINITIALIZED_REFERENCE */ 4 === $invalidBehavior ? null : $this->{$this->methodMap[$id]}();
}
} catch (\Exception $e) {
unset($this->services[$id]);
throw $e;
} finally {
unset($this->loading[$id]);
}
if (/* self::EXCEPTION_ON_INVALID_REFERENCE */ 1 === $invalidBehavior) {
if (!$id) {
throw new ServiceNotFoundException($id);
}
if (isset($this->syntheticIds[$id])) {
throw new ServiceNotFoundException($id, null, null, [], sprintf('The "%s" service is synthetic, it needs to be set at boot time before it can be used.', $id));
}
if (isset($this->getRemovedIds()[$id])) {
throw new ServiceNotFoundException($id, null, null, [], sprintf('The "%s" service or alias has been removed or inlined when the container was compiled. You should either make it public, or stop using the container directly and use dependency injection instead.', $id));
}
$alternatives = [];
foreach ($this->getServiceIds() as $knownId) {
if ('' === $knownId || '.' === $knownId[0]) {
continue;
}
$lev = levenshtein($id, $knownId);
if ($lev <= \strlen($id) / 3 || false !== strpos($knownId, $id)) {
$alternatives[] = $knownId;
}
}
throw new ServiceNotFoundException($id, null, null, $alternatives);
}
return null;
}
/**
* Returns true if the given service has actually been initialized.
*
* @param string $id The service identifier
*
* @return bool true if service has already been initialized, false otherwise
*/
public function initialized(string $id)
{
if (isset($this->aliases[$id])) {
$id = $this->aliases[$id];
}
if ('service_container' === $id) {
return false;
}
return isset($this->services[$id]);
}
/**
* {@inheritdoc}
*/
public function reset()
{
$services = $this->services + $this->privates;
$this->services = $this->factories = $this->privates = [];
foreach ($services as $service) {
try {
if ($service instanceof ResetInterface) {
$service->reset();
}
} catch (\Throwable $e) {
continue;
}
}
}
/**
* Gets all service ids.
*
* @return string[] An array of all defined service ids
*/
public function getServiceIds()
{
return array_map('strval', array_unique(array_merge(['service_container'], array_keys($this->fileMap), array_keys($this->methodMap), array_keys($this->aliases), array_keys($this->services))));
}
/**
* Gets service ids that existed at compile time.
*
* @return array
*/
public function getRemovedIds()
{
return [];
}
/**
* Camelizes a string.
*
* @param string $id A string to camelize
*
* @return string The camelized string
*/
public static function camelize($id)
{
return strtr(ucwords(strtr($id, ['_' => ' ', '.' => '_ ', '\\' => '_ '])), [' ' => '']);
}
/**
* A string to underscore.
*
* @param string $id The string to underscore
*
* @return string The underscored string
*/
public static function underscore($id)
{
return strtolower(preg_replace(['/([A-Z]+)([A-Z][a-z])/', '/([a-z\d])([A-Z])/'], ['\\1_\\2', '\\1_\\2'], str_replace('_', '.', $id)));
}
/**
* Creates a service by requiring its factory file.
*/
protected function load($file)
{
return require $file;
}
/**
* Fetches a variable from the environment.
*
* @param string $name The name of the environment variable
*
* @return mixed The value to use for the provided environment variable name
*
* @throws EnvNotFoundException When the environment variable is not found and has no default value
*/
protected function getEnv($name)
{
if (isset($this->resolving[$envName = "env($name)"])) {
throw new ParameterCircularReferenceException(array_keys($this->resolving));
}
if (isset($this->envCache[$name]) || \array_key_exists($name, $this->envCache)) {
return $this->envCache[$name];
}
if (!$this->has($id = 'container.env_var_processors_locator')) {
$this->set($id, new ServiceLocator([]));
}
if (!$this->getEnv) {
$this->getEnv = new \ReflectionMethod($this, __FUNCTION__);
$this->getEnv->setAccessible(true);
$this->getEnv = $this->getEnv->getClosure($this);
}
$processors = $this->get($id);
if (false !== $i = strpos($name, ':')) {
$prefix = substr($name, 0, $i);
$localName = substr($name, 1 + $i);
} else {
$prefix = 'string';
$localName = $name;
}
$processor = $processors->has($prefix) ? $processors->get($prefix) : new EnvVarProcessor($this);
$this->resolving[$envName] = true;
try {
return $this->envCache[$name] = $processor->getEnv($prefix, $localName, $this->getEnv);
} finally {
unset($this->resolving[$envName]);
}
}
/**
* @param string|false $registry
* @param string|bool $load
*
* @return mixed
*
* @internal
*/
final protected function getService($registry, string $id, ?string $method, $load)
{
if ('service_container' === $id) {
return $this;
}
if (\is_string($load)) {
throw new RuntimeException($load);
}
if (null === $method) {
return false !== $registry ? $this->{$registry}[$id] ?? null : null;
}
if (false !== $registry) {
return $this->{$registry}[$id] ?? $this->{$registry}[$id] = $load ? $this->load($method) : $this->{$method}();
}
if (!$load) {
return $this->{$method}();
}
return ($factory = $this->factories[$id] ?? $this->factories['service_container'][$id] ?? null) ? $factory() : $this->load($method);
}
private function __clone()
{
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection;
/**
* Represents a PHP type-hinted service reference.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
class TypedReference extends Reference
{
private $type;
private $name;
/**
* @param string $id The service identifier
* @param string $type The PHP type of the identified service
* @param int $invalidBehavior The behavior when the service does not exist
* @param string $name The name of the argument targeting the service
*/
public function __construct(string $id, string $type, int $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, string $name = null)
{
$this->name = $type === $id ? $name : null;
parent::__construct($id, $invalidBehavior);
$this->type = $type;
}
public function getType()
{
return $this->type;
}
public function getName(): ?string
{
return $this->name;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\ParameterBag;
use Symfony\Component\DependencyInjection\Container;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
class ContainerBag extends FrozenParameterBag implements ContainerBagInterface
{
private $container;
public function __construct(Container $container)
{
$this->container = $container;
}
/**
* {@inheritdoc}
*/
public function all()
{
return $this->container->getParameterBag()->all();
}
/**
* {@inheritdoc}
*/
public function get($name)
{
return $this->container->getParameter($name);
}
/**
* {@inheritdoc}
*/
public function has($name)
{
return $this->container->hasParameter($name);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\ParameterBag;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
class EnvPlaceholderParameterBag extends ParameterBag
{
private $envPlaceholderUniquePrefix;
private $envPlaceholders = [];
private $unusedEnvPlaceholders = [];
private $providedTypes = [];
private static $counter = 0;
/**
* {@inheritdoc}
*/
public function get(string $name)
{
if (0 === strpos($name, 'env(') && ')' === substr($name, -1) && 'env()' !== $name) {
$env = substr($name, 4, -1);
if (isset($this->envPlaceholders[$env])) {
foreach ($this->envPlaceholders[$env] as $placeholder) {
return $placeholder; // return first result
}
}
if (isset($this->unusedEnvPlaceholders[$env])) {
foreach ($this->unusedEnvPlaceholders[$env] as $placeholder) {
return $placeholder; // return first result
}
}
if (!preg_match('/^(?:[-.\w]*+:)*+\w++$/', $env)) {
throw new InvalidArgumentException(sprintf('Invalid %s name: only "word" characters are allowed.', $name));
}
if ($this->has($name) && null !== ($defaultValue = parent::get($name)) && !\is_string($defaultValue)) {
throw new RuntimeException(sprintf('The default value of an env() parameter must be a string or null, but "%s" given to "%s".', get_debug_type($defaultValue), $name));
}
$uniqueName = md5($name.'_'.self::$counter++);
$placeholder = sprintf('%s_%s_%s', $this->getEnvPlaceholderUniquePrefix(), strtr($env, ':-.', '___'), $uniqueName);
$this->envPlaceholders[$env][$placeholder] = $placeholder;
return $placeholder;
}
return parent::get($name);
}
/**
* Gets the common env placeholder prefix for env vars created by this bag.
*/
public function getEnvPlaceholderUniquePrefix(): string
{
if (null === $this->envPlaceholderUniquePrefix) {
$reproducibleEntropy = unserialize(serialize($this->parameters));
array_walk_recursive($reproducibleEntropy, function (&$v) { $v = null; });
$this->envPlaceholderUniquePrefix = 'env_'.substr(md5(serialize($reproducibleEntropy)), -16);
}
return $this->envPlaceholderUniquePrefix;
}
/**
* Returns the map of env vars used in the resolved parameter values to their placeholders.
*
* @return string[][] A map of env var names to their placeholders
*/
public function getEnvPlaceholders()
{
return $this->envPlaceholders;
}
public function getUnusedEnvPlaceholders(): array
{
return $this->unusedEnvPlaceholders;
}
public function clearUnusedEnvPlaceholders()
{
$this->unusedEnvPlaceholders = [];
}
/**
* Merges the env placeholders of another EnvPlaceholderParameterBag.
*/
public function mergeEnvPlaceholders(self $bag)
{
if ($newPlaceholders = $bag->getEnvPlaceholders()) {
$this->envPlaceholders += $newPlaceholders;
foreach ($newPlaceholders as $env => $placeholders) {
$this->envPlaceholders[$env] += $placeholders;
}
}
if ($newUnusedPlaceholders = $bag->getUnusedEnvPlaceholders()) {
$this->unusedEnvPlaceholders += $newUnusedPlaceholders;
foreach ($newUnusedPlaceholders as $env => $placeholders) {
$this->unusedEnvPlaceholders[$env] += $placeholders;
}
}
}
/**
* Maps env prefixes to their corresponding PHP types.
*/
public function setProvidedTypes(array $providedTypes)
{
$this->providedTypes = $providedTypes;
}
/**
* Gets the PHP types corresponding to env() parameter prefixes.
*
* @return string[][]
*/
public function getProvidedTypes()
{
return $this->providedTypes;
}
/**
* {@inheritdoc}
*/
public function resolve()
{
if ($this->resolved) {
return;
}
parent::resolve();
foreach ($this->envPlaceholders as $env => $placeholders) {
if ($this->has($name = "env($env)") && null !== ($default = $this->parameters[$name]) && !\is_string($default)) {
throw new RuntimeException(sprintf('The default value of env parameter "%s" must be a string or null, "%s" given.', $env, get_debug_type($default)));
}
}
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\ParameterBag;
use Symfony\Component\DependencyInjection\Exception\LogicException;
use Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException;
/**
* ParameterBagInterface is the interface implemented by objects that manage service container parameters.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
interface ParameterBagInterface
{
/**
* Clears all parameters.
*
* @throws LogicException if the ParameterBagInterface can not be cleared
*/
public function clear();
/**
* Adds parameters to the service container parameters.
*
* @param array $parameters An array of parameters
*
* @throws LogicException if the parameter can not be added
*/
public function add(array $parameters);
/**
* Gets the service container parameters.
*
* @return array An array of parameters
*/
public function all();
/**
* Gets a service container parameter.
*
* @return mixed The parameter value
*
* @throws ParameterNotFoundException if the parameter is not defined
*/
public function get(string $name);
/**
* Removes a parameter.
*/
public function remove(string $name);
/**
* Sets a service container parameter.
*
* @param mixed $value The parameter value
*
* @throws LogicException if the parameter can not be set
*/
public function set(string $name, $value);
/**
* Returns true if a parameter name is defined.
*
* @return bool true if the parameter name is defined, false otherwise
*/
public function has(string $name);
/**
* Replaces parameter placeholders (%name%) by their values for all parameters.
*/
public function resolve();
/**
* Replaces parameter placeholders (%name%) by their values.
*
* @param mixed $value A value
*
* @throws ParameterNotFoundException if a placeholder references a parameter that does not exist
*/
public function resolveValue($value);
/**
* Escape parameter placeholders %.
*
* @param mixed $value
*
* @return mixed
*/
public function escapeValue($value);
/**
* Unescape parameter placeholders %.
*
* @param mixed $value
*
* @return mixed
*/
public function unescapeValue($value);
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\ParameterBag;
use Symfony\Component\DependencyInjection\Exception\LogicException;
/**
* Holds read-only parameters.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class FrozenParameterBag extends ParameterBag
{
/**
* For performance reasons, the constructor assumes that
* all keys are already lowercased.
*
* This is always the case when used internally.
*
* @param array $parameters An array of parameters
*/
public function __construct(array $parameters = [])
{
$this->parameters = $parameters;
$this->resolved = true;
}
/**
* {@inheritdoc}
*/
public function clear()
{
throw new LogicException('Impossible to call clear() on a frozen ParameterBag.');
}
/**
* {@inheritdoc}
*/
public function add(array $parameters)
{
throw new LogicException('Impossible to call add() on a frozen ParameterBag.');
}
/**
* {@inheritdoc}
*/
public function set(string $name, $value)
{
throw new LogicException('Impossible to call set() on a frozen ParameterBag.');
}
/**
* {@inheritdoc}
*/
public function remove(string $name)
{
throw new LogicException('Impossible to call remove() on a frozen ParameterBag.');
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\ParameterBag;
use Symfony\Component\DependencyInjection\Exception\ParameterCircularReferenceException;
use Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
/**
* Holds parameters.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class ParameterBag implements ParameterBagInterface
{
protected $parameters = [];
protected $resolved = false;
/**
* @param array $parameters An array of parameters
*/
public function __construct(array $parameters = [])
{
$this->add($parameters);
}
/**
* Clears all parameters.
*/
public function clear()
{
$this->parameters = [];
}
/**
* Adds parameters to the service container parameters.
*
* @param array $parameters An array of parameters
*/
public function add(array $parameters)
{
foreach ($parameters as $key => $value) {
$this->set($key, $value);
}
}
/**
* {@inheritdoc}
*/
public function all()
{
return $this->parameters;
}
/**
* {@inheritdoc}
*/
public function get(string $name)
{
if (!\array_key_exists($name, $this->parameters)) {
if (!$name) {
throw new ParameterNotFoundException($name);
}
$alternatives = [];
foreach ($this->parameters as $key => $parameterValue) {
$lev = levenshtein($name, $key);
if ($lev <= \strlen($name) / 3 || false !== strpos($key, $name)) {
$alternatives[] = $key;
}
}
$nonNestedAlternative = null;
if (!\count($alternatives) && false !== strpos($name, '.')) {
$namePartsLength = array_map('strlen', explode('.', $name));
$key = substr($name, 0, -1 * (1 + array_pop($namePartsLength)));
while (\count($namePartsLength)) {
if ($this->has($key)) {
if (\is_array($this->get($key))) {
$nonNestedAlternative = $key;
}
break;
}
$key = substr($key, 0, -1 * (1 + array_pop($namePartsLength)));
}
}
throw new ParameterNotFoundException($name, null, null, null, $alternatives, $nonNestedAlternative);
}
return $this->parameters[$name];
}
/**
* Sets a service container parameter.
*
* @param string $name The parameter name
* @param mixed $value The parameter value
*/
public function set(string $name, $value)
{
$this->parameters[$name] = $value;
}
/**
* {@inheritdoc}
*/
public function has(string $name)
{
return \array_key_exists((string) $name, $this->parameters);
}
/**
* Removes a parameter.
*
* @param string $name The parameter name
*/
public function remove(string $name)
{
unset($this->parameters[$name]);
}
/**
* {@inheritdoc}
*/
public function resolve()
{
if ($this->resolved) {
return;
}
$parameters = [];
foreach ($this->parameters as $key => $value) {
try {
$value = $this->resolveValue($value);
$parameters[$key] = $this->unescapeValue($value);
} catch (ParameterNotFoundException $e) {
$e->setSourceKey($key);
throw $e;
}
}
$this->parameters = $parameters;
$this->resolved = true;
}
/**
* Replaces parameter placeholders (%name%) by their values.
*
* @param mixed $value A value
* @param array $resolving An array of keys that are being resolved (used internally to detect circular references)
*
* @return mixed The resolved value
*
* @throws ParameterNotFoundException if a placeholder references a parameter that does not exist
* @throws ParameterCircularReferenceException if a circular reference if detected
* @throws RuntimeException when a given parameter has a type problem
*/
public function resolveValue($value, array $resolving = [])
{
if (\is_array($value)) {
$args = [];
foreach ($value as $k => $v) {
$args[\is_string($k) ? $this->resolveValue($k, $resolving) : $k] = $this->resolveValue($v, $resolving);
}
return $args;
}
if (!\is_string($value) || 2 > \strlen($value)) {
return $value;
}
return $this->resolveString($value, $resolving);
}
/**
* Resolves parameters inside a string.
*
* @param array $resolving An array of keys that are being resolved (used internally to detect circular references)
*
* @return mixed The resolved string
*
* @throws ParameterNotFoundException if a placeholder references a parameter that does not exist
* @throws ParameterCircularReferenceException if a circular reference if detected
* @throws RuntimeException when a given parameter has a type problem
*/
public function resolveString(string $value, array $resolving = [])
{
// we do this to deal with non string values (Boolean, integer, ...)
// as the preg_replace_callback throw an exception when trying
// a non-string in a parameter value
if (preg_match('/^%([^%\s]+)%$/', $value, $match)) {
$key = $match[1];
if (isset($resolving[$key])) {
throw new ParameterCircularReferenceException(array_keys($resolving));
}
$resolving[$key] = true;
return $this->resolved ? $this->get($key) : $this->resolveValue($this->get($key), $resolving);
}
return preg_replace_callback('/%%|%([^%\s]+)%/', function ($match) use ($resolving, $value) {
// skip %%
if (!isset($match[1])) {
return '%%';
}
$key = $match[1];
if (isset($resolving[$key])) {
throw new ParameterCircularReferenceException(array_keys($resolving));
}
$resolved = $this->get($key);
if (!\is_string($resolved) && !is_numeric($resolved)) {
throw new RuntimeException(sprintf('A string value must be composed of strings and/or numbers, but found parameter "%s" of type "%s" inside string value "%s".', $key, get_debug_type($resolved), $value));
}
$resolved = (string) $resolved;
$resolving[$key] = true;
return $this->isResolved() ? $resolved : $this->resolveString($resolved, $resolving);
}, $value);
}
public function isResolved()
{
return $this->resolved;
}
/**
* {@inheritdoc}
*/
public function escapeValue($value)
{
if (\is_string($value)) {
return str_replace('%', '%%', $value);
}
if (\is_array($value)) {
$result = [];
foreach ($value as $k => $v) {
$result[$k] = $this->escapeValue($v);
}
return $result;
}
return $value;
}
/**
* {@inheritdoc}
*/
public function unescapeValue($value)
{
if (\is_string($value)) {
return str_replace('%%', '%', $value);
}
if (\is_array($value)) {
$result = [];
foreach ($value as $k => $v) {
$result[$k] = $this->unescapeValue($v);
}
return $result;
}
return $value;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\ParameterBag;
use Psr\Container\ContainerInterface;
use Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException;
/**
* ContainerBagInterface is the interface implemented by objects that manage service container parameters.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
interface ContainerBagInterface extends ContainerInterface
{
/**
* Gets the service container parameters.
*
* @return array An array of parameters
*/
public function all();
/**
* Replaces parameter placeholders (%name%) by their values.
*
* @param mixed $value A value
*
* @throws ParameterNotFoundException if a placeholder references a parameter that does not exist
*/
public function resolveValue($value);
/**
* Escape parameter placeholders %.
*
* @param mixed $value
*
* @return mixed
*/
public function escapeValue($value);
/**
* Unescape parameter placeholders %.
*
* @param mixed $value
*
* @return mixed
*/
public function unescapeValue($value);
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection;
/**
* Parameter represents a parameter reference.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class Parameter
{
private $id;
public function __construct(string $id)
{
$this->id = $id;
}
/**
* @return string The parameter key
*/
public function __toString()
{
return $this->id;
}
}
Copyright (c) 2004-2020 Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Exception\OutOfBoundsException;
/**
* This definition extends another definition.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class ChildDefinition extends Definition
{
private $parent;
/**
* @param string $parent The id of Definition instance to decorate
*/
public function __construct(string $parent)
{
$this->parent = $parent;
}
/**
* Returns the Definition to inherit from.
*
* @return string
*/
public function getParent()
{
return $this->parent;
}
/**
* Sets the Definition to inherit from.
*
* @param string $parent
*
* @return $this
*/
public function setParent($parent)
{
$this->parent = $parent;
return $this;
}
/**
* Gets an argument to pass to the service constructor/factory method.
*
* If replaceArgument() has been used to replace an argument, this method
* will return the replacement value.
*
* @param int|string $index
*
* @return mixed The argument value
*
* @throws OutOfBoundsException When the argument does not exist
*/
public function getArgument($index)
{
if (\array_key_exists('index_'.$index, $this->arguments)) {
return $this->arguments['index_'.$index];
}
return parent::getArgument($index);
}
/**
* You should always use this method when overwriting existing arguments
* of the parent definition.
*
* If you directly call setArguments() keep in mind that you must follow
* certain conventions when you want to overwrite the arguments of the
* parent definition, otherwise your arguments will only be appended.
*
* @param int|string $index
* @param mixed $value
*
* @return $this
*
* @throws InvalidArgumentException when $index isn't an integer
*/
public function replaceArgument($index, $value)
{
if (\is_int($index)) {
$this->arguments['index_'.$index] = $value;
} elseif (0 === strpos($index, '$')) {
$this->arguments[$index] = $value;
} else {
throw new InvalidArgumentException('The argument must be an existing index or the name of a constructor\'s parameter.');
}
return $this;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Argument;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Reference;
/**
* Represents a service wrapped in a memoizing closure.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
class ServiceClosureArgument implements ArgumentInterface
{
private $values;
public function __construct(Reference $reference)
{
$this->values = [$reference];
}
/**
* {@inheritdoc}
*/
public function getValues()
{
return $this->values;
}
/**
* {@inheritdoc}
*/
public function setValues(array $values)
{
if ([0] !== array_keys($values) || !($values[0] instanceof Reference || null === $values[0])) {
throw new InvalidArgumentException('A ServiceClosureArgument must hold one and only one Reference.');
}
$this->values = $values;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Argument;
/**
* Represents a collection of services found by tag name to lazily iterate over.
*
* @author Roland Franssen <franssen.roland@gmail.com>
*/
class TaggedIteratorArgument extends IteratorArgument
{
private $tag;
private $indexAttribute;
private $defaultIndexMethod;
private $defaultPriorityMethod;
private $needsIndexes = false;
/**
* @param string $tag The name of the tag identifying the target services
* @param string|null $indexAttribute The name of the attribute that defines the key referencing each service in the tagged collection
* @param string|null $defaultIndexMethod The static method that should be called to get each service's key when their tag doesn't define the previous attribute
* @param bool $needsIndexes Whether indexes are required and should be generated when computing the map
* @param string|null $defaultPriorityMethod The static method that should be called to get each service's priority when their tag doesn't define the "priority" attribute
*/
public function __construct(string $tag, string $indexAttribute = null, string $defaultIndexMethod = null, bool $needsIndexes = false, string $defaultPriorityMethod = null)
{
parent::__construct([]);
if (null === $indexAttribute && $needsIndexes) {
$indexAttribute = preg_match('/[^.]++$/', $tag, $m) ? $m[0] : $tag;
}
$this->tag = $tag;
$this->indexAttribute = $indexAttribute;
$this->defaultIndexMethod = $defaultIndexMethod ?: ('getDefault'.str_replace(' ', '', ucwords(preg_replace('/[^a-zA-Z0-9\x7f-\xff]++/', ' ', $indexAttribute ?? ''))).'Name');
$this->needsIndexes = $needsIndexes;
$this->defaultPriorityMethod = $defaultPriorityMethod ?: ('getDefault'.str_replace(' ', '', ucwords(preg_replace('/[^a-zA-Z0-9\x7f-\xff]++/', ' ', $indexAttribute ?? ''))).'Priority');
}
public function getTag()
{
return $this->tag;
}
public function getIndexAttribute(): ?string
{
return $this->indexAttribute;
}
public function getDefaultIndexMethod(): ?string
{
return $this->defaultIndexMethod;
}
public function needsIndexes(): bool
{
return $this->needsIndexes;
}
public function getDefaultPriorityMethod(): ?string
{
return $this->defaultPriorityMethod;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Argument;
use Symfony\Component\DependencyInjection\ServiceLocator as BaseServiceLocator;
/**
* @author Nicolas Grekas <p@tchwork.com>
*
* @internal
*/
class ServiceLocator extends BaseServiceLocator
{
private $factory;
private $serviceMap;
private $serviceTypes;
public function __construct(\Closure $factory, array $serviceMap, array $serviceTypes = null)
{
$this->factory = $factory;
$this->serviceMap = $serviceMap;
$this->serviceTypes = $serviceTypes;
parent::__construct($serviceMap);
}
/**
* {@inheritdoc}
*/
public function get($id)
{
return isset($this->serviceMap[$id]) ? ($this->factory)(...$this->serviceMap[$id]) : parent::get($id);
}
/**
* {@inheritdoc}
*/
public function getProvidedServices(): array
{
return $this->serviceTypes ?? $this->serviceTypes = array_map(function () { return '?'; }, $this->serviceMap);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Argument;
/**
* Represents an abstract service argument, which have to be set by a compiler pass or a DI extension.
*/
final class AbstractArgument
{
private $text;
private $context;
public function __construct(string $text = '')
{
$this->text = trim($text, '. ');
}
public function setContext(string $context): void
{
$this->context = $context.' is abstract'.('' === $this->text ? '' : ': ');
}
public function getText(): string
{
return $this->text;
}
public function getTextWithContext(): string
{
return $this->context.$this->text.'.';
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Argument;
/**
* Represents a complex argument containing nested values.
*
* @author Titouan Galopin <galopintitouan@gmail.com>
*/
interface ArgumentInterface
{
/**
* @return array
*/
public function getValues();
public function setValues(array $values);
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Argument;
use Symfony\Component\DependencyInjection\Reference;
/**
* Represents a closure acting as a service locator.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
class ServiceLocatorArgument implements ArgumentInterface
{
use ReferenceSetArgumentTrait;
private $taggedIteratorArgument;
/**
* @param Reference[]|TaggedIteratorArgument $values
*/
public function __construct($values = [])
{
if ($values instanceof TaggedIteratorArgument) {
$this->taggedIteratorArgument = $values;
$this->values = [];
} else {
$this->setValues($values);
}
}
public function getTaggedIteratorArgument(): ?TaggedIteratorArgument
{
return $this->taggedIteratorArgument;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Argument;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Reference;
/**
* @author Titouan Galopin <galopintitouan@gmail.com>
* @author Nicolas Grekas <p@tchwork.com>
*/
trait ReferenceSetArgumentTrait
{
private $values;
/**
* @param Reference[] $values
*/
public function __construct(array $values)
{
$this->setValues($values);
}
/**
* @return Reference[] The values in the set
*/
public function getValues()
{
return $this->values;
}
/**
* @param Reference[] $values The service references to put in the set
*/
public function setValues(array $values)
{
foreach ($values as $k => $v) {
if (null !== $v && !$v instanceof Reference) {
throw new InvalidArgumentException(sprintf('A "%s" must hold only Reference instances, "%s" given.', __CLASS__, get_debug_type($v)));
}
}
$this->values = $values;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Argument;
/**
* @author Guilhem Niot <guilhem.niot@gmail.com>
*/
final class BoundArgument implements ArgumentInterface
{
const SERVICE_BINDING = 0;
const DEFAULTS_BINDING = 1;
const INSTANCEOF_BINDING = 2;
private static $sequence = 0;
private $value;
private $identifier;
private $used;
private $type;
private $file;
public function __construct($value, bool $trackUsage = true, int $type = 0, string $file = null)
{
$this->value = $value;
if ($trackUsage) {
$this->identifier = ++self::$sequence;
} else {
$this->used = true;
}
$this->type = $type;
$this->file = $file;
}
/**
* {@inheritdoc}
*/
public function getValues(): array
{
return [$this->value, $this->identifier, $this->used, $this->type, $this->file];
}
/**
* {@inheritdoc}
*/
public function setValues(array $values)
{
if (5 === \count($values)) {
[$this->value, $this->identifier, $this->used, $this->type, $this->file] = $values;
} else {
[$this->value, $this->identifier, $this->used] = $values;
}
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Argument;
/**
* Represents a collection of values to lazily iterate over.
*
* @author Titouan Galopin <galopintitouan@gmail.com>
*/
class IteratorArgument implements ArgumentInterface
{
use ReferenceSetArgumentTrait;
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Argument;
/**
* @internal
*/
class RewindableGenerator implements \IteratorAggregate, \Countable
{
private $generator;
private $count;
/**
* @param int|callable $count
*/
public function __construct(callable $generator, $count)
{
$this->generator = $generator;
$this->count = $count;
}
public function getIterator(): \Traversable
{
$g = $this->generator;
return $g();
}
public function count(): int
{
if (\is_callable($count = $this->count)) {
$this->count = $count();
}
return $this->count;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection;
use Psr\Container\ContainerInterface as PsrContainerInterface;
use Symfony\Component\Config\Resource\ClassExistenceResource;
use Symfony\Component\Config\Resource\ComposerResource;
use Symfony\Component\Config\Resource\DirectoryResource;
use Symfony\Component\Config\Resource\FileExistenceResource;
use Symfony\Component\Config\Resource\FileResource;
use Symfony\Component\Config\Resource\GlobResource;
use Symfony\Component\Config\Resource\ReflectionClassResource;
use Symfony\Component\Config\Resource\ResourceInterface;
use Symfony\Component\DependencyInjection\Argument\AbstractArgument;
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\Argument\RewindableGenerator;
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
use Symfony\Component\DependencyInjection\Argument\ServiceLocator;
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
use Symfony\Component\DependencyInjection\Compiler\Compiler;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
use Symfony\Component\DependencyInjection\Compiler\ResolveEnvPlaceholdersPass;
use Symfony\Component\DependencyInjection\Exception\BadMethodCallException;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Exception\LogicException;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException;
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
use Symfony\Component\DependencyInjection\Extension\ExtensionInterface;
use Symfony\Component\DependencyInjection\LazyProxy\Instantiator\InstantiatorInterface;
use Symfony\Component\DependencyInjection\LazyProxy\Instantiator\RealServiceInstantiator;
use Symfony\Component\DependencyInjection\ParameterBag\EnvPlaceholderParameterBag;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\ExpressionLanguage\Expression;
use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface;
/**
* ContainerBuilder is a DI container that provides an API to easily describe services.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class ContainerBuilder extends Container implements TaggedContainerInterface
{
/**
* @var ExtensionInterface[]
*/
private $extensions = [];
/**
* @var ExtensionInterface[]
*/
private $extensionsByNs = [];
/**
* @var Definition[]
*/
private $definitions = [];
/**
* @var Alias[]
*/
private $aliasDefinitions = [];
/**
* @var ResourceInterface[]
*/
private $resources = [];
private $extensionConfigs = [];
/**
* @var Compiler
*/
private $compiler;
private $trackResources;
/**
* @var InstantiatorInterface|null
*/
private $proxyInstantiator;
/**
* @var ExpressionLanguage|null
*/
private $expressionLanguage;
/**
* @var ExpressionFunctionProviderInterface[]
*/
private $expressionLanguageProviders = [];
/**
* @var string[] with tag names used by findTaggedServiceIds
*/
private $usedTags = [];
/**
* @var string[][] a map of env var names to their placeholders
*/
private $envPlaceholders = [];
/**
* @var int[] a map of env vars to their resolution counter
*/
private $envCounters = [];
/**
* @var string[] the list of vendor directories
*/
private $vendors;
private $autoconfiguredInstanceof = [];
private $removedIds = [];
private $removedBindingIds = [];
private static $internalTypes = [
'int' => true,
'float' => true,
'string' => true,
'bool' => true,
'resource' => true,
'object' => true,
'array' => true,
'null' => true,
'callable' => true,
'iterable' => true,
'mixed' => true,
];
public function __construct(ParameterBagInterface $parameterBag = null)
{
parent::__construct($parameterBag);
$this->trackResources = interface_exists('Symfony\Component\Config\Resource\ResourceInterface');
$this->setDefinition('service_container', (new Definition(ContainerInterface::class))->setSynthetic(true)->setPublic(true));
$this->setAlias(PsrContainerInterface::class, new Alias('service_container', false))->setDeprecated('symfony/dependency-injection', '5.1', $deprecationMessage = 'The "%alias_id%" autowiring alias is deprecated. Define it explicitly in your app if you want to keep using it.');
$this->setAlias(ContainerInterface::class, new Alias('service_container', false))->setDeprecated('symfony/dependency-injection', '5.1', $deprecationMessage);
}
/**
* @var \ReflectionClass[] a list of class reflectors
*/
private $classReflectors;
/**
* Sets the track resources flag.
*
* If you are not using the loaders and therefore don't want
* to depend on the Config component, set this flag to false.
*/
public function setResourceTracking(bool $track)
{
$this->trackResources = $track;
}
/**
* Checks if resources are tracked.
*
* @return bool true If resources are tracked, false otherwise
*/
public function isTrackingResources()
{
return $this->trackResources;
}
/**
* Sets the instantiator to be used when fetching proxies.
*/
public function setProxyInstantiator(InstantiatorInterface $proxyInstantiator)
{
$this->proxyInstantiator = $proxyInstantiator;
}
public function registerExtension(ExtensionInterface $extension)
{
$this->extensions[$extension->getAlias()] = $extension;
if (false !== $extension->getNamespace()) {
$this->extensionsByNs[$extension->getNamespace()] = $extension;
}
}
/**
* Returns an extension by alias or namespace.
*
* @return ExtensionInterface An extension instance
*
* @throws LogicException if the extension is not registered
*/
public function getExtension(string $name)
{
if (isset($this->extensions[$name])) {
return $this->extensions[$name];
}
if (isset($this->extensionsByNs[$name])) {
return $this->extensionsByNs[$name];
}
throw new LogicException(sprintf('Container extension "%s" is not registered.', $name));
}
/**
* Returns all registered extensions.
*
* @return ExtensionInterface[] An array of ExtensionInterface
*/
public function getExtensions()
{
return $this->extensions;
}
/**
* Checks if we have an extension.
*
* @return bool If the extension exists
*/
public function hasExtension(string $name)
{
return isset($this->extensions[$name]) || isset($this->extensionsByNs[$name]);
}
/**
* Returns an array of resources loaded to build this configuration.
*
* @return ResourceInterface[] An array of resources
*/
public function getResources()
{
return array_values($this->resources);
}
/**
* @return $this
*/
public function addResource(ResourceInterface $resource)
{
if (!$this->trackResources) {
return $this;
}
if ($resource instanceof GlobResource && $this->inVendors($resource->getPrefix())) {
return $this;
}
$this->resources[(string) $resource] = $resource;
return $this;
}
/**
* Sets the resources for this configuration.
*
* @param ResourceInterface[] $resources An array of resources
*
* @return $this
*/
public function setResources(array $resources)
{
if (!$this->trackResources) {
return $this;
}
$this->resources = $resources;
return $this;
}
/**
* Adds the object class hierarchy as resources.
*
* @param object|string $object An object instance or class name
*
* @return $this
*/
public function addObjectResource($object)
{
if ($this->trackResources) {
if (\is_object($object)) {
$object = \get_class($object);
}
if (!isset($this->classReflectors[$object])) {
$this->classReflectors[$object] = new \ReflectionClass($object);
}
$class = $this->classReflectors[$object];
foreach ($class->getInterfaceNames() as $name) {
if (null === $interface = &$this->classReflectors[$name]) {
$interface = new \ReflectionClass($name);
}
$file = $interface->getFileName();
if (false !== $file && file_exists($file)) {
$this->fileExists($file);
}
}
do {
$file = $class->getFileName();
if (false !== $file && file_exists($file)) {
$this->fileExists($file);
}
foreach ($class->getTraitNames() as $name) {
$this->addObjectResource($name);
}
} while ($class = $class->getParentClass());
}
return $this;
}
/**
* Retrieves the requested reflection class and registers it for resource tracking.
*
* @throws \ReflectionException when a parent class/interface/trait is not found and $throw is true
*
* @final
*/
public function getReflectionClass(?string $class, bool $throw = true): ?\ReflectionClass
{
if (!$class = $this->getParameterBag()->resolveValue($class)) {
return null;
}
if (isset(self::$internalTypes[$class])) {
return null;
}
$resource = $classReflector = null;
try {
if (isset($this->classReflectors[$class])) {
$classReflector = $this->classReflectors[$class];
} elseif (class_exists(ClassExistenceResource::class)) {
$resource = new ClassExistenceResource($class, false);
$classReflector = $resource->isFresh(0) ? false : new \ReflectionClass($class);
} else {
$classReflector = class_exists($class) ? new \ReflectionClass($class) : false;
}
} catch (\ReflectionException $e) {
if ($throw) {
throw $e;
}
}
if ($this->trackResources) {
if (!$classReflector) {
$this->addResource($resource ?: new ClassExistenceResource($class, false));
} elseif (!$classReflector->isInternal()) {
$path = $classReflector->getFileName();
if (!$this->inVendors($path)) {
$this->addResource(new ReflectionClassResource($classReflector, $this->vendors));
}
}
$this->classReflectors[$class] = $classReflector;
}
return $classReflector ?: null;
}
/**
* Checks whether the requested file or directory exists and registers the result for resource tracking.
*
* @param string $path The file or directory path for which to check the existence
* @param bool|string $trackContents Whether to track contents of the given resource. If a string is passed,
* it will be used as pattern for tracking contents of the requested directory
*
* @final
*/
public function fileExists(string $path, $trackContents = true): bool
{
$exists = file_exists($path);
if (!$this->trackResources || $this->inVendors($path)) {
return $exists;
}
if (!$exists) {
$this->addResource(new FileExistenceResource($path));
return $exists;
}
if (is_dir($path)) {
if ($trackContents) {
$this->addResource(new DirectoryResource($path, \is_string($trackContents) ? $trackContents : null));
} else {
$this->addResource(new GlobResource($path, '/*', false));
}
} elseif ($trackContents) {
$this->addResource(new FileResource($path));
}
return $exists;
}
/**
* Loads the configuration for an extension.
*
* @param string $extension The extension alias or namespace
* @param array $values An array of values that customizes the extension
*
* @return $this
*
* @throws BadMethodCallException When this ContainerBuilder is compiled
* @throws \LogicException if the extension is not registered
*/
public function loadFromExtension(string $extension, array $values = null)
{
if ($this->isCompiled()) {
throw new BadMethodCallException('Cannot load from an extension on a compiled container.');
}
if (\func_num_args() < 2) {
$values = [];
}
$namespace = $this->getExtension($extension)->getAlias();
$this->extensionConfigs[$namespace][] = $values;
return $this;
}
/**
* Adds a compiler pass.
*
* @param string $type The type of compiler pass
* @param int $priority Used to sort the passes
*
* @return $this
*/
public function addCompilerPass(CompilerPassInterface $pass, string $type = PassConfig::TYPE_BEFORE_OPTIMIZATION, int $priority = 0)
{
$this->getCompiler()->addPass($pass, $type, $priority);
$this->addObjectResource($pass);
return $this;
}
/**
* Returns the compiler pass config which can then be modified.
*
* @return PassConfig The compiler pass config
*/
public function getCompilerPassConfig()
{
return $this->getCompiler()->getPassConfig();
}
/**
* Returns the compiler.
*
* @return Compiler The compiler
*/
public function getCompiler()
{
if (null === $this->compiler) {
$this->compiler = new Compiler();
}
return $this->compiler;
}
/**
* Sets a service.
*
* @throws BadMethodCallException When this ContainerBuilder is compiled
*/
public function set(string $id, ?object $service)
{
if ($this->isCompiled() && (isset($this->definitions[$id]) && !$this->definitions[$id]->isSynthetic())) {
// setting a synthetic service on a compiled container is alright
throw new BadMethodCallException(sprintf('Setting service "%s" for an unknown or non-synthetic service definition on a compiled container is not allowed.', $id));
}
unset($this->definitions[$id], $this->aliasDefinitions[$id], $this->removedIds[$id]);
parent::set($id, $service);
}
/**
* Removes a service definition.
*/
public function removeDefinition(string $id)
{
if (isset($this->definitions[$id])) {
unset($this->definitions[$id]);
$this->removedIds[$id] = true;
}
}
/**
* Returns true if the given service is defined.
*
* @param string $id The service identifier
*
* @return bool true if the service is defined, false otherwise
*/
public function has($id)
{
$id = (string) $id;
return isset($this->definitions[$id]) || isset($this->aliasDefinitions[$id]) || parent::has($id);
}
/**
* Gets a service.
*
* @param string $id The service identifier
* @param int $invalidBehavior The behavior when the service does not exist
*
* @return object|null The associated service
*
* @throws InvalidArgumentException when no definitions are available
* @throws ServiceCircularReferenceException When a circular reference is detected
* @throws ServiceNotFoundException When the service is not defined
* @throws \Exception
*
* @see Reference
*/
public function get($id, int $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE)
{
if ($this->isCompiled() && isset($this->removedIds[$id = (string) $id]) && ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE >= $invalidBehavior) {
return parent::get($id);
}
return $this->doGet($id, $invalidBehavior);
}
private function doGet(string $id, int $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, array &$inlineServices = null, bool $isConstructorArgument = false)
{
if (isset($inlineServices[$id])) {
return $inlineServices[$id];
}
if (null === $inlineServices) {
$isConstructorArgument = true;
$inlineServices = [];
}
try {
if (ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE === $invalidBehavior) {
return parent::get($id, $invalidBehavior);
}
if ($service = parent::get($id, ContainerInterface::NULL_ON_INVALID_REFERENCE)) {
return $service;
}
} catch (ServiceCircularReferenceException $e) {
if ($isConstructorArgument) {
throw $e;
}
}
if (!isset($this->definitions[$id]) && isset($this->aliasDefinitions[$id])) {
$alias = $this->aliasDefinitions[$id];
if ($alias->isDeprecated()) {
$deprecation = $alias->getDeprecation($id);
trigger_deprecation($deprecation['package'], $deprecation['version'], $deprecation['message']);
}
return $this->doGet((string) $alias, $invalidBehavior, $inlineServices, $isConstructorArgument);
}
try {
$definition = $this->getDefinition($id);
} catch (ServiceNotFoundException $e) {
if (ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE < $invalidBehavior) {
return null;
}
throw $e;
}
if ($definition->hasErrors() && $e = $definition->getErrors()) {
throw new RuntimeException(reset($e));
}
if ($isConstructorArgument) {
$this->loading[$id] = true;
}
try {
return $this->createService($definition, $inlineServices, $isConstructorArgument, $id);
} finally {
if ($isConstructorArgument) {
unset($this->loading[$id]);
}
}
}
/**
* Merges a ContainerBuilder with the current ContainerBuilder configuration.
*
* Service definitions overrides the current defined ones.
*
* But for parameters, they are overridden by the current ones. It allows
* the parameters passed to the container constructor to have precedence
* over the loaded ones.
*
* $container = new ContainerBuilder(new ParameterBag(['foo' => 'bar']));
* $loader = new LoaderXXX($container);
* $loader->load('resource_name');
* $container->register('foo', 'stdClass');
*
* In the above example, even if the loaded resource defines a foo
* parameter, the value will still be 'bar' as defined in the ContainerBuilder
* constructor.
*
* @throws BadMethodCallException When this ContainerBuilder is compiled
*/
public function merge(self $container)
{
if ($this->isCompiled()) {
throw new BadMethodCallException('Cannot merge on a compiled container.');
}
$this->addDefinitions($container->getDefinitions());
$this->addAliases($container->getAliases());
$this->getParameterBag()->add($container->getParameterBag()->all());
if ($this->trackResources) {
foreach ($container->getResources() as $resource) {
$this->addResource($resource);
}
}
foreach ($this->extensions as $name => $extension) {
if (!isset($this->extensionConfigs[$name])) {
$this->extensionConfigs[$name] = [];
}
$this->extensionConfigs[$name] = array_merge($this->extensionConfigs[$name], $container->getExtensionConfig($name));
}
if ($this->getParameterBag() instanceof EnvPlaceholderParameterBag && $container->getParameterBag() instanceof EnvPlaceholderParameterBag) {
$envPlaceholders = $container->getParameterBag()->getEnvPlaceholders();
$this->getParameterBag()->mergeEnvPlaceholders($container->getParameterBag());
} else {
$envPlaceholders = [];
}
foreach ($container->envCounters as $env => $count) {
if (!$count && !isset($envPlaceholders[$env])) {
continue;
}
if (!isset($this->envCounters[$env])) {
$this->envCounters[$env] = $count;
} else {
$this->envCounters[$env] += $count;
}
}
foreach ($container->getAutoconfiguredInstanceof() as $interface => $childDefinition) {
if (isset($this->autoconfiguredInstanceof[$interface])) {
throw new InvalidArgumentException(sprintf('"%s" has already been autoconfigured and merge() does not support merging autoconfiguration for the same class/interface.', $interface));
}
$this->autoconfiguredInstanceof[$interface] = $childDefinition;
}
}
/**
* Returns the configuration array for the given extension.
*
* @return array An array of configuration
*/
public function getExtensionConfig(string $name)
{
if (!isset($this->extensionConfigs[$name])) {
$this->extensionConfigs[$name] = [];
}
return $this->extensionConfigs[$name];
}
/**
* Prepends a config array to the configs of the given extension.
*/
public function prependExtensionConfig(string $name, array $config)
{
if (!isset($this->extensionConfigs[$name])) {
$this->extensionConfigs[$name] = [];
}
array_unshift($this->extensionConfigs[$name], $config);
}
/**
* Compiles the container.
*
* This method passes the container to compiler
* passes whose job is to manipulate and optimize
* the container.
*
* The main compiler passes roughly do four things:
*
* * The extension configurations are merged;
* * Parameter values are resolved;
* * The parameter bag is frozen;
* * Extension loading is disabled.
*
* @param bool $resolveEnvPlaceholders Whether %env()% parameters should be resolved using the current
* env vars or be replaced by uniquely identifiable placeholders.
* Set to "true" when you want to use the current ContainerBuilder
* directly, keep to "false" when the container is dumped instead.
*/
public function compile(bool $resolveEnvPlaceholders = false)
{
$compiler = $this->getCompiler();
if ($this->trackResources) {
foreach ($compiler->getPassConfig()->getPasses() as $pass) {
$this->addObjectResource($pass);
}
}
$bag = $this->getParameterBag();
if ($resolveEnvPlaceholders && $bag instanceof EnvPlaceholderParameterBag) {
$compiler->addPass(new ResolveEnvPlaceholdersPass(), PassConfig::TYPE_AFTER_REMOVING, -1000);
}
$compiler->compile($this);
foreach ($this->definitions as $id => $definition) {
if ($this->trackResources && $definition->isLazy()) {
$this->getReflectionClass($definition->getClass());
}
}
$this->extensionConfigs = [];
if ($bag instanceof EnvPlaceholderParameterBag) {
if ($resolveEnvPlaceholders) {
$this->parameterBag = new ParameterBag($this->resolveEnvPlaceholders($bag->all(), true));
}
$this->envPlaceholders = $bag->getEnvPlaceholders();
}
parent::compile();
foreach ($this->definitions + $this->aliasDefinitions as $id => $definition) {
if (!$definition->isPublic() || $definition->isPrivate()) {
$this->removedIds[$id] = true;
}
}
}
/**
* {@inheritdoc}
*/
public function getServiceIds()
{
return array_map('strval', array_unique(array_merge(array_keys($this->getDefinitions()), array_keys($this->aliasDefinitions), parent::getServiceIds())));
}
/**
* Gets removed service or alias ids.
*
* @return array
*/
public function getRemovedIds()
{
return $this->removedIds;
}
/**
* Adds the service aliases.
*/
public function addAliases(array $aliases)
{
foreach ($aliases as $alias => $id) {
$this->setAlias($alias, $id);
}
}
/**
* Sets the service aliases.
*/
public function setAliases(array $aliases)
{
$this->aliasDefinitions = [];
$this->addAliases($aliases);
}
/**
* Sets an alias for an existing service.
*
* @param string $alias The alias to create
* @param string|Alias $id The service to alias
*
* @return Alias
*
* @throws InvalidArgumentException if the id is not a string or an Alias
* @throws InvalidArgumentException if the alias is for itself
*/
public function setAlias(string $alias, $id)
{
if ('' === $alias || '\\' === $alias[-1] || \strlen($alias) !== strcspn($alias, "\0\r\n'")) {
throw new InvalidArgumentException(sprintf('Invalid alias id: "%s".', $alias));
}
if (\is_string($id)) {
$id = new Alias($id);
} elseif (!$id instanceof Alias) {
throw new InvalidArgumentException('$id must be a string, or an Alias object.');
}
if ($alias === (string) $id) {
throw new InvalidArgumentException(sprintf('An alias can not reference itself, got a circular reference on "%s".', $alias));
}
unset($this->definitions[$alias], $this->removedIds[$alias]);
return $this->aliasDefinitions[$alias] = $id;
}
/**
* Removes an alias.
*
* @param string $alias The alias to remove
*/
public function removeAlias(string $alias)
{
if (isset($this->aliasDefinitions[$alias])) {
unset($this->aliasDefinitions[$alias]);
$this->removedIds[$alias] = true;
}
}
/**
* Returns true if an alias exists under the given identifier.
*
* @return bool true if the alias exists, false otherwise
*/
public function hasAlias(string $id)
{
return isset($this->aliasDefinitions[$id]);
}
/**
* Gets all defined aliases.
*
* @return Alias[] An array of aliases
*/
public function getAliases()
{
return $this->aliasDefinitions;
}
/**
* Gets an alias.
*
* @return Alias An Alias instance
*
* @throws InvalidArgumentException if the alias does not exist
*/
public function getAlias(string $id)
{
if (!isset($this->aliasDefinitions[$id])) {
throw new InvalidArgumentException(sprintf('The service alias "%s" does not exist.', $id));
}
return $this->aliasDefinitions[$id];
}
/**
* Registers a service definition.
*
* This methods allows for simple registration of service definition
* with a fluid interface.
*
* @return Definition A Definition instance
*/
public function register(string $id, string $class = null)
{
return $this->setDefinition($id, new Definition($class));
}
/**
* Registers an autowired service definition.
*
* This method implements a shortcut for using setDefinition() with
* an autowired definition.
*
* @return Definition The created definition
*/
public function autowire(string $id, string $class = null)
{
return $this->setDefinition($id, (new Definition($class))->setAutowired(true));
}
/**
* Adds the service definitions.
*
* @param Definition[] $definitions An array of service definitions
*/
public function addDefinitions(array $definitions)
{
foreach ($definitions as $id => $definition) {
$this->setDefinition($id, $definition);
}
}
/**
* Sets the service definitions.
*
* @param Definition[] $definitions An array of service definitions
*/
public function setDefinitions(array $definitions)
{
$this->definitions = [];
$this->addDefinitions($definitions);
}
/**
* Gets all service definitions.
*
* @return Definition[] An array of Definition instances
*/
public function getDefinitions()
{
return $this->definitions;
}
/**
* Sets a service definition.
*
* @return Definition the service definition
*
* @throws BadMethodCallException When this ContainerBuilder is compiled
*/
public function setDefinition(string $id, Definition $definition)
{
if ($this->isCompiled()) {
throw new BadMethodCallException('Adding definition to a compiled container is not allowed.');
}
if ('' === $id || '\\' === $id[-1] || \strlen($id) !== strcspn($id, "\0\r\n'")) {
throw new InvalidArgumentException(sprintf('Invalid service id: "%s".', $id));
}
unset($this->aliasDefinitions[$id], $this->removedIds[$id]);
return $this->definitions[$id] = $definition;
}
/**
* Returns true if a service definition exists under the given identifier.
*
* @return bool true if the service definition exists, false otherwise
*/
public function hasDefinition(string $id)
{
return isset($this->definitions[$id]);
}
/**
* Gets a service definition.
*
* @return Definition A Definition instance
*
* @throws ServiceNotFoundException if the service definition does not exist
*/
public function getDefinition(string $id)
{
if (!isset($this->definitions[$id])) {
throw new ServiceNotFoundException($id);
}
return $this->definitions[$id];
}
/**
* Gets a service definition by id or alias.
*
* The method "unaliases" recursively to return a Definition instance.
*
* @return Definition A Definition instance
*
* @throws ServiceNotFoundException if the service definition does not exist
*/
public function findDefinition(string $id)
{
$seen = [];
while (isset($this->aliasDefinitions[$id])) {
$id = (string) $this->aliasDefinitions[$id];
if (isset($seen[$id])) {
$seen = array_values($seen);
$seen = \array_slice($seen, array_search($id, $seen));
$seen[] = $id;
throw new ServiceCircularReferenceException($id, $seen);
}
$seen[$id] = $id;
}
return $this->getDefinition($id);
}
/**
* Creates a service for a service definition.
*
* @return mixed The service described by the service definition
*
* @throws RuntimeException When the factory definition is incomplete
* @throws RuntimeException When the service is a synthetic service
* @throws InvalidArgumentException When configure callable is not callable
*/
private function createService(Definition $definition, array &$inlineServices, bool $isConstructorArgument = false, string $id = null, bool $tryProxy = true)
{
if (null === $id && isset($inlineServices[$h = spl_object_hash($definition)])) {
return $inlineServices[$h];
}
if ($definition instanceof ChildDefinition) {
throw new RuntimeException(sprintf('Constructing service "%s" from a parent definition is not supported at build time.', $id));
}
if ($definition->isSynthetic()) {
throw new RuntimeException(sprintf('You have requested a synthetic service ("%s"). The DIC does not know how to construct this service.', $id));
}
if ($definition->isDeprecated()) {
$deprecation = $definition->getDeprecation($id);
trigger_deprecation($deprecation['package'], $deprecation['version'], $deprecation['message']);
}
if ($tryProxy && $definition->isLazy() && !$tryProxy = !($proxy = $this->proxyInstantiator) || $proxy instanceof RealServiceInstantiator) {
$proxy = $proxy->instantiateProxy(
$this,
$definition,
$id, function () use ($definition, &$inlineServices, $id) {
return $this->createService($definition, $inlineServices, true, $id, false);
}
);
$this->shareService($definition, $proxy, $id, $inlineServices);
return $proxy;
}
$parameterBag = $this->getParameterBag();
if (null !== $definition->getFile()) {
require_once $parameterBag->resolveValue($definition->getFile());
}
$arguments = $this->doResolveServices($parameterBag->unescapeValue($parameterBag->resolveValue($definition->getArguments())), $inlineServices, $isConstructorArgument);
if (null !== $factory = $definition->getFactory()) {
if (\is_array($factory)) {
$factory = [$this->doResolveServices($parameterBag->resolveValue($factory[0]), $inlineServices, $isConstructorArgument), $factory[1]];
} elseif (!\is_string($factory)) {
throw new RuntimeException(sprintf('Cannot create service "%s" because of invalid factory.', $id));
}
}
if (null !== $id && $definition->isShared() && isset($this->services[$id]) && ($tryProxy || !$definition->isLazy())) {
return $this->services[$id];
}
if (null !== $factory) {
$service = $factory(...$arguments);
if (!$definition->isDeprecated() && \is_array($factory) && \is_string($factory[0])) {
$r = new \ReflectionClass($factory[0]);
if (0 < strpos($r->getDocComment(), "\n * @deprecated ")) {
trigger_deprecation('', '', 'The "%s" service relies on the deprecated "%s" factory class. It should either be deprecated or its factory upgraded.', $id, $r->name);
}
}
} else {
$r = new \ReflectionClass($parameterBag->resolveValue($definition->getClass()));
$service = null === $r->getConstructor() ? $r->newInstance() : $r->newInstanceArgs(array_values($arguments));
if (!$definition->isDeprecated() && 0 < strpos($r->getDocComment(), "\n * @deprecated ")) {
trigger_deprecation('', '', 'The "%s" service relies on the deprecated "%s" class. It should either be deprecated or its implementation upgraded.', $id, $r->name);
}
}
$lastWitherIndex = null;
foreach ($definition->getMethodCalls() as $k => $call) {
if ($call[2] ?? false) {
$lastWitherIndex = $k;
}
}
if (null === $lastWitherIndex && ($tryProxy || !$definition->isLazy())) {
// share only if proxying failed, or if not a proxy, and if no withers are found
$this->shareService($definition, $service, $id, $inlineServices);
}
$properties = $this->doResolveServices($parameterBag->unescapeValue($parameterBag->resolveValue($definition->getProperties())), $inlineServices);
foreach ($properties as $name => $value) {
$service->$name = $value;
}
foreach ($definition->getMethodCalls() as $k => $call) {
$service = $this->callMethod($service, $call, $inlineServices);
if ($lastWitherIndex === $k && ($tryProxy || !$definition->isLazy())) {
// share only if proxying failed, or if not a proxy, and this is the last wither
$this->shareService($definition, $service, $id, $inlineServices);
}
}
if ($callable = $definition->getConfigurator()) {
if (\is_array($callable)) {
$callable[0] = $parameterBag->resolveValue($callable[0]);
if ($callable[0] instanceof Reference) {
$callable[0] = $this->doGet((string) $callable[0], $callable[0]->getInvalidBehavior(), $inlineServices);
} elseif ($callable[0] instanceof Definition) {
$callable[0] = $this->createService($callable[0], $inlineServices);
}
}
if (!\is_callable($callable)) {
throw new InvalidArgumentException(sprintf('The configure callable for class "%s" is not a callable.', get_debug_type($service)));
}
$callable($service);
}
return $service;
}
/**
* Replaces service references by the real service instance and evaluates expressions.
*
* @param mixed $value A value
*
* @return mixed The same value with all service references replaced by
* the real service instances and all expressions evaluated
*/
public function resolveServices($value)
{
return $this->doResolveServices($value);
}
private function doResolveServices($value, array &$inlineServices = [], bool $isConstructorArgument = false)
{
if (\is_array($value)) {
foreach ($value as $k => $v) {
$value[$k] = $this->doResolveServices($v, $inlineServices, $isConstructorArgument);
}
} elseif ($value instanceof ServiceClosureArgument) {
$reference = $value->getValues()[0];
$value = function () use ($reference) {
return $this->resolveServices($reference);
};
} elseif ($value instanceof IteratorArgument) {
$value = new RewindableGenerator(function () use ($value, &$inlineServices) {
foreach ($value->getValues() as $k => $v) {
foreach (self::getServiceConditionals($v) as $s) {
if (!$this->has($s)) {
continue 2;
}
}
foreach (self::getInitializedConditionals($v) as $s) {
if (!$this->doGet($s, ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE, $inlineServices)) {
continue 2;
}
}
yield $k => $this->doResolveServices($v, $inlineServices);
}
}, function () use ($value): int {
$count = 0;
foreach ($value->getValues() as $v) {
foreach (self::getServiceConditionals($v) as $s) {
if (!$this->has($s)) {
continue 2;
}
}
foreach (self::getInitializedConditionals($v) as $s) {
if (!$this->doGet($s, ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE)) {
continue 2;
}
}
++$count;
}
return $count;
});
} elseif ($value instanceof ServiceLocatorArgument) {
$refs = $types = [];
foreach ($value->getValues() as $k => $v) {
if ($v) {
$refs[$k] = [$v];
$types[$k] = $v instanceof TypedReference ? $v->getType() : '?';
}
}
$value = new ServiceLocator(\Closure::fromCallable([$this, 'resolveServices']), $refs, $types);
} elseif ($value instanceof Reference) {
$value = $this->doGet((string) $value, $value->getInvalidBehavior(), $inlineServices, $isConstructorArgument);
} elseif ($value instanceof Definition) {
$value = $this->createService($value, $inlineServices, $isConstructorArgument);
} elseif ($value instanceof Parameter) {
$value = $this->getParameter((string) $value);
} elseif ($value instanceof Expression) {
$value = $this->getExpressionLanguage()->evaluate($value, ['container' => $this]);
} elseif ($value instanceof AbstractArgument) {
throw new RuntimeException($value->getTextWithContext());
}
return $value;
}
/**
* Returns service ids for a given tag.
*
* Example:
*
* $container->register('foo')->addTag('my.tag', ['hello' => 'world']);
*
* $serviceIds = $container->findTaggedServiceIds('my.tag');
* foreach ($serviceIds as $serviceId => $tags) {
* foreach ($tags as $tag) {
* echo $tag['hello'];
* }
* }
*
* @return array An array of tags with the tagged service as key, holding a list of attribute arrays
*/
public function findTaggedServiceIds(string $name, bool $throwOnAbstract = false)
{
$this->usedTags[] = $name;
$tags = [];
foreach ($this->getDefinitions() as $id => $definition) {
if ($definition->hasTag($name)) {
if ($throwOnAbstract && $definition->isAbstract()) {
throw new InvalidArgumentException(sprintf('The service "%s" tagged "%s" must not be abstract.', $id, $name));
}
$tags[$id] = $definition->getTag($name);
}
}
return $tags;
}
/**
* Returns all tags the defined services use.
*
* @return array An array of tags
*/
public function findTags()
{
$tags = [];
foreach ($this->getDefinitions() as $id => $definition) {
$tags = array_merge(array_keys($definition->getTags()), $tags);
}
return array_unique($tags);
}
/**
* Returns all tags not queried by findTaggedServiceIds.
*
* @return string[] An array of tags
*/
public function findUnusedTags()
{
return array_values(array_diff($this->findTags(), $this->usedTags));
}
public function addExpressionLanguageProvider(ExpressionFunctionProviderInterface $provider)
{
$this->expressionLanguageProviders[] = $provider;
}
/**
* @return ExpressionFunctionProviderInterface[]
*/
public function getExpressionLanguageProviders()
{
return $this->expressionLanguageProviders;
}
/**
* Returns a ChildDefinition that will be used for autoconfiguring the interface/class.
*
* @return ChildDefinition
*/
public function registerForAutoconfiguration(string $interface)
{
if (!isset($this->autoconfiguredInstanceof[$interface])) {
$this->autoconfiguredInstanceof[$interface] = new ChildDefinition('');
}
return $this->autoconfiguredInstanceof[$interface];
}
/**
* Registers an autowiring alias that only binds to a specific argument name.
*
* The argument name is derived from $name if provided (from $id otherwise)
* using camel case: "foo.bar" or "foo_bar" creates an alias bound to
* "$fooBar"-named arguments with $type as type-hint. Such arguments will
* receive the service $id when autowiring is used.
*/
public function registerAliasForArgument(string $id, string $type, string $name = null): Alias
{
$name = lcfirst(str_replace(' ', '', ucwords(preg_replace('/[^a-zA-Z0-9\x7f-\xff]++/', ' ', $name ?? $id))));
if (!preg_match('/^[a-zA-Z_\x7f-\xff]/', $name)) {
throw new InvalidArgumentException(sprintf('Invalid argument name "%s" for service "%s": the first character must be a letter.', $name, $id));
}
return $this->setAlias($type.' $'.$name, $id);
}
/**
* Returns an array of ChildDefinition[] keyed by interface.
*
* @return ChildDefinition[]
*/
public function getAutoconfiguredInstanceof()
{
return $this->autoconfiguredInstanceof;
}
/**
* Resolves env parameter placeholders in a string or an array.
*
* @param mixed $value The value to resolve
* @param string|true|null $format A sprintf() format returning the replacement for each env var name or
* null to resolve back to the original "%env(VAR)%" format or
* true to resolve to the actual values of the referenced env vars
* @param array &$usedEnvs Env vars found while resolving are added to this array
*
* @return mixed The value with env parameters resolved if a string or an array is passed
*/
public function resolveEnvPlaceholders($value, $format = null, array &$usedEnvs = null)
{
if (null === $format) {
$format = '%%env(%s)%%';
}
$bag = $this->getParameterBag();
if (true === $format) {
$value = $bag->resolveValue($value);
}
if ($value instanceof Definition) {
$value = (array) $value;
}
if (\is_array($value)) {
$result = [];
foreach ($value as $k => $v) {
$result[\is_string($k) ? $this->resolveEnvPlaceholders($k, $format, $usedEnvs) : $k] = $this->resolveEnvPlaceholders($v, $format, $usedEnvs);
}
return $result;
}
if (!\is_string($value) || 38 > \strlen($value)) {
return $value;
}
$envPlaceholders = $bag instanceof EnvPlaceholderParameterBag ? $bag->getEnvPlaceholders() : $this->envPlaceholders;
$completed = false;
foreach ($envPlaceholders as $env => $placeholders) {
foreach ($placeholders as $placeholder) {
if (false !== stripos($value, $placeholder)) {
if (true === $format) {
$resolved = $bag->escapeValue($this->getEnv($env));
} else {
$resolved = sprintf($format, $env);
}
if ($placeholder === $value) {
$value = $resolved;
$completed = true;
} else {
if (!\is_string($resolved) && !is_numeric($resolved)) {
throw new RuntimeException(sprintf('A string value must be composed of strings and/or numbers, but found parameter "env(%s)" of type "%s" inside string value "%s".', $env, get_debug_type($resolved), $this->resolveEnvPlaceholders($value)));
}
$value = str_ireplace($placeholder, $resolved, $value);
}
$usedEnvs[$env] = $env;
$this->envCounters[$env] = isset($this->envCounters[$env]) ? 1 + $this->envCounters[$env] : 1;
if ($completed) {
break 2;
}
}
}
}
return $value;
}
/**
* Get statistics about env usage.
*
* @return int[] The number of time each env vars has been resolved
*/
public function getEnvCounters()
{
$bag = $this->getParameterBag();
$envPlaceholders = $bag instanceof EnvPlaceholderParameterBag ? $bag->getEnvPlaceholders() : $this->envPlaceholders;
foreach ($envPlaceholders as $env => $placeholders) {
if (!isset($this->envCounters[$env])) {
$this->envCounters[$env] = 0;
}
}
return $this->envCounters;
}
/**
* @final
*/
public function log(CompilerPassInterface $pass, string $message)
{
$this->getCompiler()->log($pass, $this->resolveEnvPlaceholders($message));
}
/**
* Gets removed binding ids.
*
* @internal
*/
public function getRemovedBindingIds(): array
{
return $this->removedBindingIds;
}
/**
* Removes bindings for a service.
*
* @internal
*/
public function removeBindings(string $id)
{
if ($this->hasDefinition($id)) {
foreach ($this->getDefinition($id)->getBindings() as $key => $binding) {
[, $bindingId] = $binding->getValues();
$this->removedBindingIds[(int) $bindingId] = true;
}
}
}
/**
* Returns the Service Conditionals.
*
* @param mixed $value An array of conditionals to return
*
* @internal
*/
public static function getServiceConditionals($value): array
{
$services = [];
if (\is_array($value)) {
foreach ($value as $v) {
$services = array_unique(array_merge($services, self::getServiceConditionals($v)));
}
} elseif ($value instanceof Reference && ContainerInterface::IGNORE_ON_INVALID_REFERENCE === $value->getInvalidBehavior()) {
$services[] = (string) $value;
}
return $services;
}
/**
* Returns the initialized conditionals.
*
* @param mixed $value An array of conditionals to return
*
* @internal
*/
public static function getInitializedConditionals($value): array
{
$services = [];
if (\is_array($value)) {
foreach ($value as $v) {
$services = array_unique(array_merge($services, self::getInitializedConditionals($v)));
}
} elseif ($value instanceof Reference && ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE === $value->getInvalidBehavior()) {
$services[] = (string) $value;
}
return $services;
}
/**
* Computes a reasonably unique hash of a value.
*
* @param mixed $value A serializable value
*
* @return string
*/
public static function hash($value)
{
$hash = substr(base64_encode(hash('sha256', serialize($value), true)), 0, 7);
return str_replace(['/', '+'], ['.', '_'], $hash);
}
/**
* {@inheritdoc}
*/
protected function getEnv($name)
{
$value = parent::getEnv($name);
$bag = $this->getParameterBag();
if (!\is_string($value) || !$bag instanceof EnvPlaceholderParameterBag) {
return $value;
}
$envPlaceholders = $bag->getEnvPlaceholders();
if (isset($envPlaceholders[$name][$value])) {
$bag = new ParameterBag($bag->all());
return $bag->unescapeValue($bag->get("env($name)"));
}
foreach ($envPlaceholders as $env => $placeholders) {
if (isset($placeholders[$value])) {
return $this->getEnv($env);
}
}
$this->resolving["env($name)"] = true;
try {
return $bag->unescapeValue($this->resolveEnvPlaceholders($bag->escapeValue($value), true));
} finally {
unset($this->resolving["env($name)"]);
}
}
private function callMethod($service, array $call, array &$inlineServices)
{
foreach (self::getServiceConditionals($call[1]) as $s) {
if (!$this->has($s)) {
return $service;
}
}
foreach (self::getInitializedConditionals($call[1]) as $s) {
if (!$this->doGet($s, ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE, $inlineServices)) {
return $service;
}
}
$result = $service->{$call[0]}(...$this->doResolveServices($this->getParameterBag()->unescapeValue($this->getParameterBag()->resolveValue($call[1])), $inlineServices));
return empty($call[2]) ? $service : $result;
}
/**
* Shares a given service in the container.
*
* @param mixed $service
*/
private function shareService(Definition $definition, $service, ?string $id, array &$inlineServices)
{
$inlineServices[null !== $id ? $id : spl_object_hash($definition)] = $service;
if (null !== $id && $definition->isShared()) {
$this->services[$id] = $service;
unset($this->loading[$id]);
}
}
private function getExpressionLanguage(): ExpressionLanguage
{
if (null === $this->expressionLanguage) {
if (!class_exists('Symfony\Component\ExpressionLanguage\ExpressionLanguage')) {
throw new LogicException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed.');
}
$this->expressionLanguage = new ExpressionLanguage(null, $this->expressionLanguageProviders);
}
return $this->expressionLanguage;
}
private function inVendors(string $path): bool
{
if (null === $this->vendors) {
$resource = new ComposerResource();
$this->vendors = $resource->getVendors();
$this->addResource($resource);
}
$path = realpath($path) ?: $path;
foreach ($this->vendors as $vendor) {
if (0 === strpos($path, $vendor) && false !== strpbrk(substr($path, \strlen($vendor), 1), '/'.\DIRECTORY_SEPARATOR)) {
return true;
}
}
return false;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Exception;
use Psr\Container\ContainerExceptionInterface;
/**
* Base ExceptionInterface for Dependency Injection component.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Bulat Shakirzyanov <bulat@theopenskyproject.com>
*/
interface ExceptionInterface extends ContainerExceptionInterface, \Throwable
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Exception;
use Psr\Container\NotFoundExceptionInterface;
/**
* This exception is thrown when a non-existent service is requested.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class ServiceNotFoundException extends InvalidArgumentException implements NotFoundExceptionInterface
{
private $id;
private $sourceId;
private $alternatives;
public function __construct(string $id, string $sourceId = null, \Throwable $previous = null, array $alternatives = [], string $msg = null)
{
if (null !== $msg) {
// no-op
} elseif (null === $sourceId) {
$msg = sprintf('You have requested a non-existent service "%s".', $id);
} else {
$msg = sprintf('The service "%s" has a dependency on a non-existent service "%s".', $sourceId, $id);
}
if ($alternatives) {
if (1 == \count($alternatives)) {
$msg .= ' Did you mean this: "';
} else {
$msg .= ' Did you mean one of these: "';
}
$msg .= implode('", "', $alternatives).'"?';
}
parent::__construct($msg, 0, $previous);
$this->id = $id;
$this->sourceId = $sourceId;
$this->alternatives = $alternatives;
}
public function getId()
{
return $this->id;
}
public function getSourceId()
{
return $this->sourceId;
}
public function getAlternatives()
{
return $this->alternatives;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Exception;
/**
* This exception is thrown when a circular reference is detected.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class ServiceCircularReferenceException extends RuntimeException
{
private $serviceId;
private $path;
public function __construct(string $serviceId, array $path, \Throwable $previous = null)
{
parent::__construct(sprintf('Circular reference detected for service "%s", path: "%s".', $serviceId, implode(' -> ', $path)), 0, $previous);
$this->serviceId = $serviceId;
$this->path = $path;
}
public function getServiceId()
{
return $this->serviceId;
}
public function getPath()
{
return $this->path;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Exception;
/**
* Base RuntimeException for Dependency Injection component.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class RuntimeException extends \RuntimeException implements ExceptionInterface
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Exception;
/**
* Base BadMethodCallException for Dependency Injection component.
*/
class BadMethodCallException extends \BadMethodCallException implements ExceptionInterface
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Exception;
/**
* Base OutOfBoundsException for Dependency Injection component.
*/
class OutOfBoundsException extends \OutOfBoundsException implements ExceptionInterface
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Exception;
/**
* This exception wraps exceptions whose messages contain a reference to an env parameter.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
class EnvParameterException extends InvalidArgumentException
{
public function __construct(array $envs, \Throwable $previous = null, string $message = 'Incompatible use of dynamic environment variables "%s" found in parameters.')
{
parent::__construct(sprintf($message, implode('", "', $envs)), 0, $previous);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Exception;
use Psr\Container\NotFoundExceptionInterface;
/**
* This exception is thrown when a non-existent parameter is used.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class ParameterNotFoundException extends InvalidArgumentException implements NotFoundExceptionInterface
{
private $key;
private $sourceId;
private $sourceKey;
private $alternatives;
private $nonNestedAlternative;
/**
* @param string $key The requested parameter key
* @param string $sourceId The service id that references the non-existent parameter
* @param string $sourceKey The parameter key that references the non-existent parameter
* @param \Throwable $previous The previous exception
* @param string[] $alternatives Some parameter name alternatives
* @param string|null $nonNestedAlternative The alternative parameter name when the user expected dot notation for nested parameters
*/
public function __construct(string $key, string $sourceId = null, string $sourceKey = null, \Throwable $previous = null, array $alternatives = [], string $nonNestedAlternative = null)
{
$this->key = $key;
$this->sourceId = $sourceId;
$this->sourceKey = $sourceKey;
$this->alternatives = $alternatives;
$this->nonNestedAlternative = $nonNestedAlternative;
parent::__construct('', 0, $previous);
$this->updateRepr();
}
public function updateRepr()
{
if (null !== $this->sourceId) {
$this->message = sprintf('The service "%s" has a dependency on a non-existent parameter "%s".', $this->sourceId, $this->key);
} elseif (null !== $this->sourceKey) {
$this->message = sprintf('The parameter "%s" has a dependency on a non-existent parameter "%s".', $this->sourceKey, $this->key);
} else {
$this->message = sprintf('You have requested a non-existent parameter "%s".', $this->key);
}
if ($this->alternatives) {
if (1 == \count($this->alternatives)) {
$this->message .= ' Did you mean this: "';
} else {
$this->message .= ' Did you mean one of these: "';
}
$this->message .= implode('", "', $this->alternatives).'"?';
} elseif (null !== $this->nonNestedAlternative) {
$this->message .= ' You cannot access nested array items, do you want to inject "'.$this->nonNestedAlternative.'" instead?';
}
}
public function getKey()
{
return $this->key;
}
public function getSourceId()
{
return $this->sourceId;
}
public function getSourceKey()
{
return $this->sourceKey;
}
public function setSourceId($sourceId)
{
$this->sourceId = $sourceId;
$this->updateRepr();
}
public function setSourceKey($sourceKey)
{
$this->sourceKey = $sourceKey;
$this->updateRepr();
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Exception;
/**
* Thrown when a definition cannot be autowired.
*/
class AutowiringFailedException extends RuntimeException
{
private $serviceId;
private $messageCallback;
public function __construct(string $serviceId, $message = '', int $code = 0, \Throwable $previous = null)
{
$this->serviceId = $serviceId;
if ($message instanceof \Closure && \function_exists('xdebug_is_enabled') && xdebug_is_enabled()) {
$message = $message();
}
if (!$message instanceof \Closure) {
parent::__construct($message, $code, $previous);
return;
}
$this->messageCallback = $message;
parent::__construct('', $code, $previous);
$this->message = new class($this->message, $this->messageCallback) {
private $message;
private $messageCallback;
public function __construct(&$message, &$messageCallback)
{
$this->message = &$message;
$this->messageCallback = &$messageCallback;
}
public function __toString(): string
{
$messageCallback = $this->messageCallback;
$this->messageCallback = null;
try {
return $this->message = $messageCallback();
} catch (\Throwable $e) {
return $this->message = $e->getMessage();
}
}
};
}
public function getMessageCallback(): ?\Closure
{
return $this->messageCallback;
}
public function getServiceId()
{
return $this->serviceId;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Exception;
/**
* Thrown when trying to inject a parameter into a constructor/method with an incompatible type.
*
* @author Nicolas Grekas <p@tchwork.com>
* @author Julien Maulny <jmaulny@darkmira.fr>
*/
class InvalidParameterTypeException extends InvalidArgumentException
{
public function __construct(string $serviceId, string $type, \ReflectionParameter $parameter)
{
$acceptedType = $parameter->getType();
$acceptedType = $acceptedType instanceof \ReflectionNamedType ? $acceptedType->getName() : (string) $acceptedType;
$this->code = $type;
parent::__construct(sprintf('Invalid definition for service "%s": argument %d of "%s::%s" accepts "%s", "%s" passed.', $serviceId, 1 + $parameter->getPosition(), $parameter->getDeclaringClass()->getName(), $parameter->getDeclaringFunction()->getName(), $acceptedType, $type));
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Exception;
/**
* This exception is thrown when an environment variable is not found.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
class EnvNotFoundException extends InvalidArgumentException
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Exception;
/**
* Base LogicException for Dependency Injection component.
*/
class LogicException extends \LogicException implements ExceptionInterface
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Exception;
/**
* Base InvalidArgumentException for Dependency Injection component.
*
* @author Bulat Shakirzyanov <bulat@theopenskyproject.com>
*/
class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Exception;
/**
* This exception is thrown when a circular reference in a parameter is detected.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class ParameterCircularReferenceException extends RuntimeException
{
private $parameters;
public function __construct(array $parameters, \Throwable $previous = null)
{
parent::__construct(sprintf('Circular reference detected for parameter "%s" ("%s" > "%s").', $parameters[0], implode('" > "', $parameters), $parameters[0]), 0, $previous);
$this->parameters = $parameters;
}
public function getParameters()
{
return $this->parameters;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection;
/**
* EnvVarLoaderInterface objects return key/value pairs that are added to the list of available env vars.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
interface EnvVarLoaderInterface
{
/**
* @return string[] Key/value pairs that can be accessed using the regular "%env()%" syntax
*/
public function loadEnvVars(): array;
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
class Alias
{
private $id;
private $public;
private $deprecation = [];
private static $defaultDeprecationTemplate = 'The "%alias_id%" service alias is deprecated. You should stop using it, as it will be removed in the future.';
public function __construct(string $id, bool $public = false)
{
$this->id = $id;
$this->public = $public;
}
/**
* Checks if this DI Alias should be public or not.
*
* @return bool
*/
public function isPublic()
{
return $this->public;
}
/**
* Sets if this Alias is public.
*
* @return $this
*/
public function setPublic(bool $boolean)
{
$this->public = $boolean;
return $this;
}
/**
* Sets if this Alias is private.
*
* @return $this
*
* @deprecated since Symfony 5.2, use setPublic() instead
*/
public function setPrivate(bool $boolean)
{
trigger_deprecation('symfony/dependency-injection', '5.2', 'The "%s()" method is deprecated, use "setPublic()" instead.', __METHOD__);
return $this->setPublic(!$boolean);
}
/**
* Whether this alias is private.
*
* @return bool
*/
public function isPrivate()
{
return !$this->public;
}
/**
* Whether this alias is deprecated, that means it should not be referenced
* anymore.
*
* @param string $package The name of the composer package that is triggering the deprecation
* @param string $version The version of the package that introduced the deprecation
* @param string $message The deprecation message to use
*
* @return $this
*
* @throws InvalidArgumentException when the message template is invalid
*/
public function setDeprecated(/* string $package, string $version, string $message */)
{
$args = \func_get_args();
if (\func_num_args() < 3) {
trigger_deprecation('symfony/dependency-injection', '5.1', 'The signature of method "%s()" requires 3 arguments: "string $package, string $version, string $message", not defining them is deprecated.', __METHOD__);
$status = $args[0] ?? true;
if (!$status) {
trigger_deprecation('symfony/dependency-injection', '5.1', 'Passing a null message to un-deprecate a node is deprecated.');
}
$message = (string) ($args[1] ?? null);
$package = $version = '';
} else {
$status = true;
$package = (string) $args[0];
$version = (string) $args[1];
$message = (string) $args[2];
}
if ('' !== $message) {
if (preg_match('#[\r\n]|\*/#', $message)) {
throw new InvalidArgumentException('Invalid characters found in deprecation template.');
}
if (false === strpos($message, '%alias_id%')) {
throw new InvalidArgumentException('The deprecation template must contain the "%alias_id%" placeholder.');
}
}
$this->deprecation = $status ? ['package' => $package, 'version' => $version, 'message' => $message ?: self::$defaultDeprecationTemplate] : [];
return $this;
}
public function isDeprecated(): bool
{
return (bool) $this->deprecation;
}
/**
* @deprecated since Symfony 5.1, use "getDeprecation()" instead.
*/
public function getDeprecationMessage(string $id): string
{
trigger_deprecation('symfony/dependency-injection', '5.1', 'The "%s()" method is deprecated, use "getDeprecation()" instead.', __METHOD__);
return $this->getDeprecation($id)['message'];
}
/**
* @param string $id Service id relying on this definition
*/
public function getDeprecation(string $id): array
{
return [
'package' => $this->deprecation['package'],
'version' => $this->deprecation['version'],
'message' => str_replace('%alias_id%', $id, $this->deprecation['message']),
];
}
/**
* Returns the Id of this alias.
*
* @return string The alias id
*/
public function __toString()
{
return $this->id;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException;
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
use Symfony\Contracts\Service\ServiceLocatorTrait;
use Symfony\Contracts\Service\ServiceProviderInterface;
use Symfony\Contracts\Service\ServiceSubscriberInterface;
/**
* @author Robin Chalas <robin.chalas@gmail.com>
* @author Nicolas Grekas <p@tchwork.com>
*/
class ServiceLocator implements ServiceProviderInterface
{
use ServiceLocatorTrait {
get as private doGet;
}
private $externalId;
private $container;
public function get($id)
{
if (!$this->externalId) {
return $this->doGet($id);
}
try {
return $this->doGet($id);
} catch (RuntimeException $e) {
$what = sprintf('service "%s" required by "%s"', $id, $this->externalId);
$message = preg_replace('/service "\.service_locator\.[^"]++"/', $what, $e->getMessage());
if ($e->getMessage() === $message) {
$message = sprintf('Cannot resolve %s: %s', $what, $message);
}
$r = new \ReflectionProperty($e, 'message');
$r->setAccessible(true);
$r->setValue($e, $message);
throw $e;
}
}
public function __invoke(string $id)
{
return isset($this->factories[$id]) ? $this->get($id) : null;
}
/**
* @internal
*
* @return static
*/
public function withContext(string $externalId, Container $container): self
{
$locator = clone $this;
$locator->externalId = $externalId;
$locator->container = $container;
return $locator;
}
private function createNotFoundException(string $id): NotFoundExceptionInterface
{
if ($this->loading) {
$msg = sprintf('The service "%s" has a dependency on a non-existent service "%s". This locator %s', end($this->loading), $id, $this->formatAlternatives());
return new ServiceNotFoundException($id, end($this->loading) ?: null, null, [], $msg);
}
$class = debug_backtrace(\DEBUG_BACKTRACE_PROVIDE_OBJECT | \DEBUG_BACKTRACE_IGNORE_ARGS, 4);
$class = isset($class[3]['object']) ? \get_class($class[3]['object']) : null;
$externalId = $this->externalId ?: $class;
$msg = [];
$msg[] = sprintf('Service "%s" not found:', $id);
if (!$this->container) {
$class = null;
} elseif ($this->container->has($id) || isset($this->container->getRemovedIds()[$id])) {
$msg[] = 'even though it exists in the app\'s container,';
} else {
try {
$this->container->get($id);
$class = null;
} catch (ServiceNotFoundException $e) {
if ($e->getAlternatives()) {
$msg[] = sprintf('did you mean %s? Anyway,', $this->formatAlternatives($e->getAlternatives(), 'or'));
} else {
$class = null;
}
}
}
if ($externalId) {
$msg[] = sprintf('the container inside "%s" is a smaller service locator that %s', $externalId, $this->formatAlternatives());
} else {
$msg[] = sprintf('the current service locator %s', $this->formatAlternatives());
}
if (!$class) {
// no-op
} elseif (is_subclass_of($class, ServiceSubscriberInterface::class)) {
$msg[] = sprintf('Unless you need extra laziness, try using dependency injection instead. Otherwise, you need to declare it using "%s::getSubscribedServices()".', preg_replace('/([^\\\\]++\\\\)++/', '', $class));
} else {
$msg[] = 'Try using dependency injection instead.';
}
return new ServiceNotFoundException($id, end($this->loading) ?: null, null, [], implode(' ', $msg));
}
private function createCircularReferenceException(string $id, array $path): ContainerExceptionInterface
{
return new ServiceCircularReferenceException($id, $path);
}
private function formatAlternatives(array $alternatives = null, string $separator = 'and'): string
{
$format = '"%s"%s';
if (null === $alternatives) {
if (!$alternatives = array_keys($this->factories)) {
return 'is empty...';
}
$format = sprintf('only knows about the %s service%s.', $format, 1 < \count($alternatives) ? 's' : '');
}
$last = array_pop($alternatives);
return sprintf($format, $alternatives ? implode('", "', $alternatives) : $last, $alternatives ? sprintf(' %s "%s"', $separator, $last) : '');
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection;
use Psr\Container\ContainerInterface;
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
/**
* Turns public and "container.reversible" services back to their ids.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
final class ReverseContainer
{
private $serviceContainer;
private $reversibleLocator;
private $tagName;
private $getServiceId;
public function __construct(Container $serviceContainer, ContainerInterface $reversibleLocator, string $tagName = 'container.reversible')
{
$this->serviceContainer = $serviceContainer;
$this->reversibleLocator = $reversibleLocator;
$this->tagName = $tagName;
$this->getServiceId = \Closure::bind(function (object $service): ?string {
return array_search($service, $this->services, true) ?: array_search($service, $this->privates, true) ?: null;
}, $serviceContainer, Container::class);
}
/**
* Returns the id of the passed object when it exists as a service.
*
* To be reversible, services need to be either public or be tagged with "container.reversible".
*/
public function getId(object $service): ?string
{
if ($this->serviceContainer === $service) {
return 'service_container';
}
if (null === $id = ($this->getServiceId)($service)) {
return null;
}
if ($this->serviceContainer->has($id) || $this->reversibleLocator->has($id)) {
return $id;
}
return null;
}
/**
* @throws ServiceNotFoundException When the service is not reversible
*/
public function getService(string $id): object
{
if ($this->serviceContainer->has($id)) {
return $this->serviceContainer->get($id);
}
if ($this->reversibleLocator->has($id)) {
return $this->reversibleLocator->get($id);
}
if (isset($this->serviceContainer->getRemovedIds()[$id])) {
throw new ServiceNotFoundException($id, null, null, [], sprintf('The "%s" service is private and cannot be accessed by reference. You should either make it public, or tag it as "%s".', $id, $this->tagName));
}
// will throw a ServiceNotFoundException
$this->serviceContainer->get($id);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection;
/**
* ContainerAware trait.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
trait ContainerAwareTrait
{
/**
* @var ContainerInterface
*/
protected $container;
public function setContainer(ContainerInterface $container = null)
{
$this->container = $container;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection;
/**
* Reference represents a service reference.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class Reference
{
private $id;
private $invalidBehavior;
public function __construct(string $id, int $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE)
{
$this->id = $id;
$this->invalidBehavior = $invalidBehavior;
}
/**
* @return string The service identifier
*/
public function __toString()
{
return $this->id;
}
/**
* Returns the behavior to be used when the service does not exist.
*
* @return int
*/
public function getInvalidBehavior()
{
return $this->invalidBehavior;
}
}
<?xml version="1.0" encoding="UTF-8" ?>
<xsd:schema xmlns="http://symfony.com/schema/dic/services"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://symfony.com/schema/dic/services"
elementFormDefault="qualified">
<xsd:annotation>
<xsd:documentation><![CDATA[
Symfony XML Services Schema, version 1.0
Authors: Fabien Potencier
This defines a way to describe PHP objects (services) and their
dependencies.
]]></xsd:documentation>
</xsd:annotation>
<xsd:element name="container" type="container" />
<xsd:complexType name="container">
<xsd:annotation>
<xsd:documentation><![CDATA[
The root element of a service file.
]]></xsd:documentation>
</xsd:annotation>
<xsd:sequence>
<xsd:group ref="foreign" />
<xsd:sequence minOccurs="0">
<xsd:element name="imports" type="imports" />
<xsd:group ref="foreign" />
</xsd:sequence>
<xsd:sequence minOccurs="0">
<xsd:element name="parameters" type="parameters" />
<xsd:group ref="foreign" />
</xsd:sequence>
<xsd:sequence minOccurs="0">
<xsd:element name="services" type="services" />
<xsd:group ref="foreign" />
</xsd:sequence>
</xsd:sequence>
</xsd:complexType>
<xsd:group name="foreign">
<xsd:sequence>
<xsd:any namespace="##other" processContents="lax" minOccurs="0" maxOccurs="unbounded" />
</xsd:sequence>
</xsd:group>
<xsd:complexType name="services">
<xsd:annotation>
<xsd:documentation><![CDATA[
Enclosing element for the definition of all services
]]></xsd:documentation>
</xsd:annotation>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="service" type="service" minOccurs="1" />
<xsd:element name="prototype" type="prototype" minOccurs="0" />
<xsd:element name="defaults" type="defaults" minOccurs="0" maxOccurs="1" />
<xsd:element name="instanceof" type="instanceof" minOccurs="0" />
<xsd:element name="stack" type="stack" minOccurs="0" />
</xsd:choice>
</xsd:complexType>
<xsd:complexType name="imports">
<xsd:annotation>
<xsd:documentation><![CDATA[
Enclosing element for the import elements
]]></xsd:documentation>
</xsd:annotation>
<xsd:choice minOccurs="1" maxOccurs="unbounded">
<xsd:element name="import" type="import" />
</xsd:choice>
</xsd:complexType>
<xsd:complexType name="import">
<xsd:annotation>
<xsd:documentation><![CDATA[
Import an external resource defining other services or parameters
]]></xsd:documentation>
</xsd:annotation>
<xsd:attribute name="resource" type="xsd:string" use="required" />
<xsd:attribute name="ignore-errors" type="ignore_errors" />
<xsd:attribute name="type" type="xsd:string" />
</xsd:complexType>
<xsd:complexType name="callable">
<xsd:choice minOccurs="0" maxOccurs="1">
<xsd:element name="service" type="service" minOccurs="0" maxOccurs="1" />
</xsd:choice>
<xsd:attribute name="service" type="xsd:string" />
<xsd:attribute name="class" type="xsd:string" />
<xsd:attribute name="method" type="xsd:string" />
<xsd:attribute name="function" type="xsd:string" />
</xsd:complexType>
<xsd:complexType name="defaults">
<xsd:annotation>
<xsd:documentation><![CDATA[
Enclosing element for the service definitions' defaults for the current file
]]></xsd:documentation>
</xsd:annotation>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="tag" type="tag" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="bind" type="bind" minOccurs="0" maxOccurs="unbounded" />
</xsd:choice>
<xsd:attribute name="public" type="boolean" />
<xsd:attribute name="autowire" type="boolean" />
<xsd:attribute name="autoconfigure" type="boolean" />
</xsd:complexType>
<xsd:complexType name="service">
<xsd:choice maxOccurs="unbounded">
<xsd:element name="file" type="xsd:string" minOccurs="0" maxOccurs="1" />
<xsd:element name="argument" type="argument" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="configurator" type="callable" minOccurs="0" maxOccurs="1" />
<xsd:element name="factory" type="callable" minOccurs="0" maxOccurs="1" />
<xsd:element name="deprecated" type="deprecated" minOccurs="0" maxOccurs="1" />
<xsd:element name="call" type="call" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="tag" type="tag" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="property" type="property" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="bind" type="bind" minOccurs="0" maxOccurs="unbounded" />
</xsd:choice>
<xsd:attribute name="id" type="xsd:string" />
<xsd:attribute name="class" type="xsd:string" />
<xsd:attribute name="shared" type="boolean" />
<xsd:attribute name="public" type="boolean" />
<xsd:attribute name="synthetic" type="boolean" />
<xsd:attribute name="lazy" type="xsd:string" />
<xsd:attribute name="abstract" type="boolean" />
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="parent" type="xsd:string" />
<xsd:attribute name="decorates" type="xsd:string" />
<xsd:attribute name="decoration-on-invalid" type="invalid_decorated_service_sequence" />
<xsd:attribute name="decoration-inner-name" type="xsd:string" />
<xsd:attribute name="decoration-priority" type="xsd:integer" />
<xsd:attribute name="autowire" type="boolean" />
<xsd:attribute name="autoconfigure" type="boolean" />
</xsd:complexType>
<xsd:complexType name="instanceof">
<xsd:choice maxOccurs="unbounded">
<xsd:element name="configurator" type="callable" minOccurs="0" maxOccurs="1" />
<xsd:element name="call" type="call" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="tag" type="tag" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="property" type="property" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="bind" type="bind" minOccurs="0" maxOccurs="unbounded" />
</xsd:choice>
<xsd:attribute name="id" type="xsd:string" use="required" />
<xsd:attribute name="shared" type="boolean" />
<xsd:attribute name="public" type="boolean" />
<xsd:attribute name="lazy" type="xsd:string" />
<xsd:attribute name="autowire" type="boolean" />
<xsd:attribute name="autoconfigure" type="boolean" />
</xsd:complexType>
<xsd:complexType name="prototype">
<xsd:choice maxOccurs="unbounded">
<xsd:element name="argument" type="argument" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="configurator" type="callable" minOccurs="0" maxOccurs="1" />
<xsd:element name="factory" type="callable" minOccurs="0" maxOccurs="1" />
<xsd:element name="deprecated" type="deprecated" minOccurs="0" maxOccurs="1" />
<xsd:element name="call" type="call" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="tag" type="tag" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="property" type="property" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="bind" type="bind" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="exclude" type="xsd:string" minOccurs="0" maxOccurs="unbounded" />
</xsd:choice>
<xsd:attribute name="namespace" type="xsd:string" use="required" />
<xsd:attribute name="resource" type="xsd:string" use="required" />
<xsd:attribute name="exclude" type="xsd:string" />
<xsd:attribute name="shared" type="boolean" />
<xsd:attribute name="public" type="boolean" />
<xsd:attribute name="lazy" type="xsd:string" />
<xsd:attribute name="abstract" type="boolean" />
<xsd:attribute name="parent" type="xsd:string" />
<xsd:attribute name="autowire" type="boolean" />
<xsd:attribute name="autoconfigure" type="boolean" />
</xsd:complexType>
<xsd:complexType name="stack">
<xsd:choice maxOccurs="unbounded">
<xsd:element name="service" type="service" minOccurs="1" />
<xsd:element name="deprecated" type="deprecated" minOccurs="0" maxOccurs="1" />
</xsd:choice>
<xsd:attribute name="id" type="xsd:string" use="required" />
<xsd:attribute name="public" type="boolean" />
</xsd:complexType>
<xsd:complexType name="tag">
<xsd:simpleContent>
<xsd:extension base="xsd:string">
<xsd:anyAttribute namespace="##any" processContents="lax" />
</xsd:extension>
</xsd:simpleContent>
</xsd:complexType>
<xsd:complexType name="deprecated">
<xsd:simpleContent>
<xsd:extension base="xsd:string">
<!-- In Symfony 6, make these attributes required -->
<xsd:attribute name="package" type="xsd:string" />
<xsd:attribute name="version" type="xsd:string" />
</xsd:extension>
</xsd:simpleContent>
</xsd:complexType>
<xsd:complexType name="parameters">
<xsd:choice minOccurs="1" maxOccurs="unbounded">
<xsd:element name="parameter" type="parameter" />
</xsd:choice>
<xsd:attribute name="type" type="parameter_type" />
<xsd:attribute name="key" type="xsd:string" />
</xsd:complexType>
<xsd:complexType name="parameter" mixed="true">
<xsd:choice minOccurs="0" maxOccurs="unbounded">
<xsd:element name="parameter" type="parameter" />
</xsd:choice>
<xsd:attribute name="type" type="parameter_type" />
<xsd:attribute name="id" type="xsd:string" />
<xsd:attribute name="key" type="xsd:string" />
<xsd:attribute name="on-invalid" type="invalid_sequence" />
</xsd:complexType>
<xsd:complexType name="property" mixed="true">
<xsd:choice minOccurs="0">
<xsd:element name="property" type="property" maxOccurs="unbounded" />
<xsd:element name="service" type="service" />
</xsd:choice>
<xsd:attribute name="type" type="argument_type" />
<xsd:attribute name="id" type="xsd:string" />
<xsd:attribute name="key" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
<xsd:attribute name="on-invalid" type="invalid_sequence" />
<xsd:attribute name="tag" type="xsd:string" />
</xsd:complexType>
<xsd:complexType name="bind" mixed="true">
<xsd:choice maxOccurs="unbounded">
<xsd:element name="bind" type="argument" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="service" type="service" />
</xsd:choice>
<xsd:attribute name="type" type="argument_type" />
<xsd:attribute name="id" type="xsd:string" />
<xsd:attribute name="key" type="xsd:string" use="required" />
<xsd:attribute name="on-invalid" type="invalid_sequence" />
<xsd:attribute name="method" type="xsd:string" />
<xsd:attribute name="tag" type="xsd:string" />
</xsd:complexType>
<xsd:complexType name="argument" mixed="true">
<xsd:choice minOccurs="0">
<xsd:element name="argument" type="argument" maxOccurs="unbounded" />
<xsd:element name="service" type="service" />
</xsd:choice>
<xsd:attribute name="type" type="argument_type" />
<xsd:attribute name="id" type="xsd:string" />
<xsd:attribute name="key" type="xsd:string" />
<xsd:attribute name="index" type="xsd:integer" />
<xsd:attribute name="on-invalid" type="invalid_sequence" />
<xsd:attribute name="tag" type="xsd:string" />
<xsd:attribute name="index-by" type="xsd:string" />
<xsd:attribute name="default-index-method" type="xsd:string" />
<xsd:attribute name="default-priority-method" type="xsd:string" />
</xsd:complexType>
<xsd:complexType name="call">
<xsd:choice minOccurs="0">
<xsd:element name="argument" type="argument" maxOccurs="unbounded" />
</xsd:choice>
<xsd:attribute name="method" type="xsd:string" />
<xsd:attribute name="returns-clone" type="boolean" />
</xsd:complexType>
<xsd:simpleType name="parameter_type">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="collection" />
<xsd:enumeration value="string" />
<xsd:enumeration value="constant" />
<xsd:enumeration value="binary" />
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="argument_type">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="abstract" />
<xsd:enumeration value="collection" />
<xsd:enumeration value="service" />
<xsd:enumeration value="expression" />
<xsd:enumeration value="string" />
<xsd:enumeration value="constant" />
<xsd:enumeration value="binary" />
<xsd:enumeration value="iterator" />
<xsd:enumeration value="service_locator" />
<!-- "tagged" is an alias of "tagged_iterator", using "tagged_iterator" is preferred. -->
<xsd:enumeration value="tagged" />
<xsd:enumeration value="tagged_iterator" />
<xsd:enumeration value="tagged_locator" />
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ignore_errors">
<xsd:restriction base="xsd:string">
<xsd:pattern value="(true|false|not_found)" />
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="invalid_sequence">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="null" />
<xsd:enumeration value="ignore" />
<xsd:enumeration value="exception" />
<xsd:enumeration value="ignore_uninitialized" />
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="invalid_decorated_service_sequence">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="null" />
<xsd:enumeration value="ignore" />
<xsd:enumeration value="exception" />
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="boolean">
<xsd:restriction base="xsd:string">
<xsd:pattern value="(%.+%|true|false)" />
</xsd:restriction>
</xsd:simpleType>
</xsd:schema>
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader;
use Symfony\Component\Config\Util\XmlUtils;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
/**
* IniFileLoader loads parameters from INI files.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class IniFileLoader extends FileLoader
{
/**
* {@inheritdoc}
*/
public function load($resource, string $type = null)
{
$path = $this->locator->locate($resource);
$this->container->fileExists($path);
// first pass to catch parsing errors
$result = parse_ini_file($path, true);
if (false === $result || [] === $result) {
throw new InvalidArgumentException(sprintf('The "%s" file is not valid.', $resource));
}
// real raw parsing
$result = parse_ini_file($path, true, \INI_SCANNER_RAW);
if (isset($result['parameters']) && \is_array($result['parameters'])) {
foreach ($result['parameters'] as $key => $value) {
$this->container->setParameter($key, $this->phpize($value));
}
}
}
/**
* {@inheritdoc}
*/
public function supports($resource, string $type = null)
{
if (!\is_string($resource)) {
return false;
}
if (null === $type && 'ini' === pathinfo($resource, \PATHINFO_EXTENSION)) {
return true;
}
return 'ini' === $type;
}
/**
* Note that the following features are not supported:
* * strings with escaped quotes are not supported "foo\"bar";
* * string concatenation ("foo" "bar").
*
* @return mixed
*/
private function phpize(string $value)
{
// trim on the right as comments removal keep whitespaces
if ($value !== $v = rtrim($value)) {
$value = '""' === substr_replace($v, '', 1, -1) ? substr($v, 1, -1) : $v;
}
$lowercaseValue = strtolower($value);
switch (true) {
case \defined($value):
return \constant($value);
case 'yes' === $lowercaseValue || 'on' === $lowercaseValue:
return true;
case 'no' === $lowercaseValue || 'off' === $lowercaseValue || 'none' === $lowercaseValue:
return false;
case isset($value[1]) && (
("'" === $value[0] && "'" === $value[\strlen($value) - 1]) ||
('"' === $value[0] && '"' === $value[\strlen($value) - 1])
):
// quoted string
return substr($value, 1, -1);
default:
return XmlUtils::phpize($value);
}
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader;
use Symfony\Component\Config\Loader\Loader;
use Symfony\Component\DependencyInjection\ContainerBuilder;
/**
* ClosureLoader loads service definitions from a PHP closure.
*
* The Closure has access to the container as its first argument.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class ClosureLoader extends Loader
{
private $container;
public function __construct(ContainerBuilder $container)
{
$this->container = $container;
}
/**
* {@inheritdoc}
*/
public function load($resource, string $type = null)
{
$resource($this->container);
}
/**
* {@inheritdoc}
*/
public function supports($resource, string $type = null)
{
return $resource instanceof \Closure;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
/**
* PhpFileLoader loads service definitions from a PHP file.
*
* The PHP file is required and the $container variable can be
* used within the file to change the container.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class PhpFileLoader extends FileLoader
{
protected $autoRegisterAliasesForSinglyImplementedInterfaces = false;
/**
* {@inheritdoc}
*/
public function load($resource, $type = null)
{
// the container and loader variables are exposed to the included file below
$container = $this->container;
$loader = $this;
$path = $this->locator->locate($resource);
$this->setCurrentDir(\dirname($path));
$this->container->fileExists($path);
// the closure forbids access to the private scope in the included file
$load = \Closure::bind(function ($path) use ($container, $loader, $resource, $type) {
return include $path;
}, $this, ProtectedPhpFileLoader::class);
try {
$callback = $load($path);
if (\is_object($callback) && \is_callable($callback)) {
$callback(new ContainerConfigurator($this->container, $this, $this->instanceof, $path, $resource), $this->container, $this);
}
} finally {
$this->instanceof = [];
$this->registerAliasesForSinglyImplementedInterfaces();
}
}
/**
* {@inheritdoc}
*/
public function supports($resource, string $type = null)
{
if (!\is_string($resource)) {
return false;
}
if (null === $type && 'php' === pathinfo($resource, \PATHINFO_EXTENSION)) {
return true;
}
return 'php' === $type;
}
}
/**
* @internal
*/
final class ProtectedPhpFileLoader extends PhpFileLoader
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader;
use Symfony\Component\DependencyInjection\Alias;
use Symfony\Component\DependencyInjection\Argument\AbstractArgument;
use Symfony\Component\DependencyInjection\Argument\ArgumentInterface;
use Symfony\Component\DependencyInjection\Argument\BoundArgument;
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
use Symfony\Component\DependencyInjection\Extension\ExtensionInterface;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\ExpressionLanguage\Expression;
use Symfony\Component\Yaml\Exception\ParseException;
use Symfony\Component\Yaml\Parser as YamlParser;
use Symfony\Component\Yaml\Tag\TaggedValue;
use Symfony\Component\Yaml\Yaml;
/**
* YamlFileLoader loads YAML files service definitions.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class YamlFileLoader extends FileLoader
{
private static $serviceKeywords = [
'alias' => 'alias',
'parent' => 'parent',
'class' => 'class',
'shared' => 'shared',
'synthetic' => 'synthetic',
'lazy' => 'lazy',
'public' => 'public',
'abstract' => 'abstract',
'deprecated' => 'deprecated',
'factory' => 'factory',
'file' => 'file',
'arguments' => 'arguments',
'properties' => 'properties',
'configurator' => 'configurator',
'calls' => 'calls',
'tags' => 'tags',
'decorates' => 'decorates',
'decoration_inner_name' => 'decoration_inner_name',
'decoration_priority' => 'decoration_priority',
'decoration_on_invalid' => 'decoration_on_invalid',
'autowire' => 'autowire',
'autoconfigure' => 'autoconfigure',
'bind' => 'bind',
];
private static $prototypeKeywords = [
'resource' => 'resource',
'namespace' => 'namespace',
'exclude' => 'exclude',
'parent' => 'parent',
'shared' => 'shared',
'lazy' => 'lazy',
'public' => 'public',
'abstract' => 'abstract',
'deprecated' => 'deprecated',
'factory' => 'factory',
'arguments' => 'arguments',
'properties' => 'properties',
'configurator' => 'configurator',
'calls' => 'calls',
'tags' => 'tags',
'autowire' => 'autowire',
'autoconfigure' => 'autoconfigure',
'bind' => 'bind',
];
private static $instanceofKeywords = [
'shared' => 'shared',
'lazy' => 'lazy',
'public' => 'public',
'properties' => 'properties',
'configurator' => 'configurator',
'calls' => 'calls',
'tags' => 'tags',
'autowire' => 'autowire',
'bind' => 'bind',
];
private static $defaultsKeywords = [
'public' => 'public',
'tags' => 'tags',
'autowire' => 'autowire',
'autoconfigure' => 'autoconfigure',
'bind' => 'bind',
];
private $yamlParser;
private $anonymousServicesCount;
private $anonymousServicesSuffix;
protected $autoRegisterAliasesForSinglyImplementedInterfaces = false;
/**
* {@inheritdoc}
*/
public function load($resource, string $type = null)
{
$path = $this->locator->locate($resource);
$content = $this->loadFile($path);
$this->container->fileExists($path);
// empty file
if (null === $content) {
return;
}
// imports
$this->parseImports($content, $path);
// parameters
if (isset($content['parameters'])) {
if (!\is_array($content['parameters'])) {
throw new InvalidArgumentException(sprintf('The "parameters" key should contain an array in "%s". Check your YAML syntax.', $path));
}
foreach ($content['parameters'] as $key => $value) {
$this->container->setParameter($key, $this->resolveServices($value, $path, true));
}
}
// extensions
$this->loadFromExtensions($content);
// services
$this->anonymousServicesCount = 0;
$this->anonymousServicesSuffix = '~'.ContainerBuilder::hash($path);
$this->setCurrentDir(\dirname($path));
try {
$this->parseDefinitions($content, $path);
} finally {
$this->instanceof = [];
$this->registerAliasesForSinglyImplementedInterfaces();
}
}
/**
* {@inheritdoc}
*/
public function supports($resource, string $type = null)
{
if (!\is_string($resource)) {
return false;
}
if (null === $type && \in_array(pathinfo($resource, \PATHINFO_EXTENSION), ['yaml', 'yml'], true)) {
return true;
}
return \in_array($type, ['yaml', 'yml'], true);
}
private function parseImports(array $content, string $file)
{
if (!isset($content['imports'])) {
return;
}
if (!\is_array($content['imports'])) {
throw new InvalidArgumentException(sprintf('The "imports" key should contain an array in "%s". Check your YAML syntax.', $file));
}
$defaultDirectory = \dirname($file);
foreach ($content['imports'] as $import) {
if (!\is_array($import)) {
$import = ['resource' => $import];
}
if (!isset($import['resource'])) {
throw new InvalidArgumentException(sprintf('An import should provide a resource in "%s". Check your YAML syntax.', $file));
}
$this->setCurrentDir($defaultDirectory);
$this->import($import['resource'], $import['type'] ?? null, $import['ignore_errors'] ?? false, $file);
}
}
private function parseDefinitions(array $content, string $file)
{
if (!isset($content['services'])) {
return;
}
if (!\is_array($content['services'])) {
throw new InvalidArgumentException(sprintf('The "services" key should contain an array in "%s". Check your YAML syntax.', $file));
}
if (\array_key_exists('_instanceof', $content['services'])) {
$instanceof = $content['services']['_instanceof'];
unset($content['services']['_instanceof']);
if (!\is_array($instanceof)) {
throw new InvalidArgumentException(sprintf('Service "_instanceof" key must be an array, "%s" given in "%s".', get_debug_type($instanceof), $file));
}
$this->instanceof = [];
$this->isLoadingInstanceof = true;
foreach ($instanceof as $id => $service) {
if (!$service || !\is_array($service)) {
throw new InvalidArgumentException(sprintf('Type definition "%s" must be a non-empty array within "_instanceof" in "%s". Check your YAML syntax.', $id, $file));
}
if (\is_string($service) && 0 === strpos($service, '@')) {
throw new InvalidArgumentException(sprintf('Type definition "%s" cannot be an alias within "_instanceof" in "%s". Check your YAML syntax.', $id, $file));
}
$this->parseDefinition($id, $service, $file, []);
}
}
$this->isLoadingInstanceof = false;
$defaults = $this->parseDefaults($content, $file);
foreach ($content['services'] as $id => $service) {
$this->parseDefinition($id, $service, $file, $defaults);
}
}
/**
* @throws InvalidArgumentException
*/
private function parseDefaults(array &$content, string $file): array
{
if (!\array_key_exists('_defaults', $content['services'])) {
return [];
}
$defaults = $content['services']['_defaults'];
unset($content['services']['_defaults']);
if (!\is_array($defaults)) {
throw new InvalidArgumentException(sprintf('Service "_defaults" key must be an array, "%s" given in "%s".', get_debug_type($defaults), $file));
}
foreach ($defaults as $key => $default) {
if (!isset(self::$defaultsKeywords[$key])) {
throw new InvalidArgumentException(sprintf('The configuration key "%s" cannot be used to define a default value in "%s". Allowed keys are "%s".', $key, $file, implode('", "', self::$defaultsKeywords)));
}
}
if (isset($defaults['tags'])) {
if (!\is_array($tags = $defaults['tags'])) {
throw new InvalidArgumentException(sprintf('Parameter "tags" in "_defaults" must be an array in "%s". Check your YAML syntax.', $file));
}
foreach ($tags as $tag) {
if (!\is_array($tag)) {
$tag = ['name' => $tag];
}
if (1 === \count($tag) && \is_array(current($tag))) {
$name = key($tag);
$tag = current($tag);
} else {
if (!isset($tag['name'])) {
throw new InvalidArgumentException(sprintf('A "tags" entry in "_defaults" is missing a "name" key in "%s".', $file));
}
$name = $tag['name'];
unset($tag['name']);
}
if (!\is_string($name) || '' === $name) {
throw new InvalidArgumentException(sprintf('The tag name in "_defaults" must be a non-empty string in "%s".', $file));
}
foreach ($tag as $attribute => $value) {
if (!is_scalar($value) && null !== $value) {
throw new InvalidArgumentException(sprintf('Tag "%s", attribute "%s" in "_defaults" must be of a scalar-type in "%s". Check your YAML syntax.', $name, $attribute, $file));
}
}
}
}
if (isset($defaults['bind'])) {
if (!\is_array($defaults['bind'])) {
throw new InvalidArgumentException(sprintf('Parameter "bind" in "_defaults" must be an array in "%s". Check your YAML syntax.', $file));
}
foreach ($this->resolveServices($defaults['bind'], $file) as $argument => $value) {
$defaults['bind'][$argument] = new BoundArgument($value, true, BoundArgument::DEFAULTS_BINDING, $file);
}
}
return $defaults;
}
private function isUsingShortSyntax(array $service): bool
{
foreach ($service as $key => $value) {
if (\is_string($key) && ('' === $key || ('$' !== $key[0] && false === strpos($key, '\\')))) {
return false;
}
}
return true;
}
/**
* Parses a definition.
*
* @param array|string $service
*
* @throws InvalidArgumentException When tags are invalid
*/
private function parseDefinition(string $id, $service, string $file, array $defaults, bool $return = false)
{
if (preg_match('/^_[a-zA-Z0-9_]*$/', $id)) {
throw new InvalidArgumentException(sprintf('Service names that start with an underscore are reserved. Rename the "%s" service or define it in XML instead.', $id));
}
if (\is_string($service) && 0 === strpos($service, '@')) {
$alias = new Alias(substr($service, 1));
if (isset($defaults['public'])) {
$alias->setPublic($defaults['public']);
}
return $return ? $alias : $this->container->setAlias($id, $alias);
}
if (\is_array($service) && $this->isUsingShortSyntax($service)) {
$service = ['arguments' => $service];
}
if (null === $service) {
$service = [];
}
if (!\is_array($service)) {
throw new InvalidArgumentException(sprintf('A service definition must be an array or a string starting with "@" but "%s" found for service "%s" in "%s". Check your YAML syntax.', get_debug_type($service), $id, $file));
}
if (isset($service['stack'])) {
if (!\is_array($service['stack'])) {
throw new InvalidArgumentException(sprintf('A stack must be an array of definitions, "%s" given for service "%s" in "%s". Check your YAML syntax.', get_debug_type($service), $id, $file));
}
$stack = [];
foreach ($service['stack'] as $k => $frame) {
if (\is_array($frame) && 1 === \count($frame) && !isset(self::$serviceKeywords[key($frame)])) {
$frame = [
'class' => key($frame),
'arguments' => current($frame),
];
}
if (\is_array($frame) && isset($frame['stack'])) {
throw new InvalidArgumentException(sprintf('Service stack "%s" cannot contain another stack in "%s".', $id, $file));
}
$definition = $this->parseDefinition($id.'" at index "'.$k, $frame, $file, $defaults, true);
if ($definition instanceof Definition) {
$definition->setInstanceofConditionals($this->instanceof);
}
$stack[$k] = $definition;
}
if ($diff = array_diff(array_keys($service), ['stack', 'public', 'deprecated'])) {
throw new InvalidArgumentException(sprintf('Invalid attribute "%s"; supported ones are "public" and "deprecated" for service "%s" in "%s". Check your YAML syntax.', implode('", "', $diff), $id, $file));
}
$service = [
'parent' => '',
'arguments' => $stack,
'tags' => ['container.stack'],
'public' => $service['public'] ?? null,
'deprecated' => $service['deprecated'] ?? null,
];
}
$this->checkDefinition($id, $service, $file);
if (isset($service['alias'])) {
$alias = new Alias($service['alias']);
if (isset($service['public'])) {
$alias->setPublic($service['public']);
} elseif (isset($defaults['public'])) {
$alias->setPublic($defaults['public']);
}
foreach ($service as $key => $value) {
if (!\in_array($key, ['alias', 'public', 'deprecated'])) {
throw new InvalidArgumentException(sprintf('The configuration key "%s" is unsupported for the service "%s" which is defined as an alias in "%s". Allowed configuration keys for service aliases are "alias", "public" and "deprecated".', $key, $id, $file));
}
if ('deprecated' === $key) {
$deprecation = \is_array($value) ? $value : ['message' => $value];
if (!isset($deprecation['package'])) {
trigger_deprecation('symfony/dependency-injection', '5.1', 'Not setting the attribute "package" of the "deprecated" option in "%s" is deprecated.', $file);
}
if (!isset($deprecation['version'])) {
trigger_deprecation('symfony/dependency-injection', '5.1', 'Not setting the attribute "version" of the "deprecated" option in "%s" is deprecated.', $file);
}
$alias->setDeprecated($deprecation['package'] ?? '', $deprecation['version'] ?? '', $deprecation['message']);
}
}
return $return ? $alias : $this->container->setAlias($id, $alias);
}
if ($this->isLoadingInstanceof) {
$definition = new ChildDefinition('');
} elseif (isset($service['parent'])) {
if ('' !== $service['parent'] && '@' === $service['parent'][0]) {
throw new InvalidArgumentException(sprintf('The value of the "parent" option for the "%s" service must be the id of the service without the "@" prefix (replace "%s" with "%s").', $id, $service['parent'], substr($service['parent'], 1)));
}
$definition = new ChildDefinition($service['parent']);
} else {
$definition = new Definition();
}
if (isset($defaults['public'])) {
$definition->setPublic($defaults['public']);
}
if (isset($defaults['autowire'])) {
$definition->setAutowired($defaults['autowire']);
}
if (isset($defaults['autoconfigure'])) {
$definition->setAutoconfigured($defaults['autoconfigure']);
}
$definition->setChanges([]);
if (isset($service['class'])) {
$definition->setClass($service['class']);
}
if (isset($service['shared'])) {
$definition->setShared($service['shared']);
}
if (isset($service['synthetic'])) {
$definition->setSynthetic($service['synthetic']);
}
if (isset($service['lazy'])) {
$definition->setLazy((bool) $service['lazy']);
if (\is_string($service['lazy'])) {
$definition->addTag('proxy', ['interface' => $service['lazy']]);
}
}
if (isset($service['public'])) {
$definition->setPublic($service['public']);
}
if (isset($service['abstract'])) {
$definition->setAbstract($service['abstract']);
}
if (isset($service['deprecated'])) {
$deprecation = \is_array($service['deprecated']) ? $service['deprecated'] : ['message' => $service['deprecated']];
if (!isset($deprecation['package'])) {
trigger_deprecation('symfony/dependency-injection', '5.1', 'Not setting the attribute "package" of the "deprecated" option in "%s" is deprecated.', $file);
}
if (!isset($deprecation['version'])) {
trigger_deprecation('symfony/dependency-injection', '5.1', 'Not setting the attribute "version" of the "deprecated" option in "%s" is deprecated.', $file);
}
$definition->setDeprecated($deprecation['package'] ?? '', $deprecation['version'] ?? '', $deprecation['message']);
}
if (isset($service['factory'])) {
$definition->setFactory($this->parseCallable($service['factory'], 'factory', $id, $file));
}
if (isset($service['file'])) {
$definition->setFile($service['file']);
}
if (isset($service['arguments'])) {
$definition->setArguments($this->resolveServices($service['arguments'], $file));
}
if (isset($service['properties'])) {
$definition->setProperties($this->resolveServices($service['properties'], $file));
}
if (isset($service['configurator'])) {
$definition->setConfigurator($this->parseCallable($service['configurator'], 'configurator', $id, $file));
}
if (isset($service['calls'])) {
if (!\is_array($service['calls'])) {
throw new InvalidArgumentException(sprintf('Parameter "calls" must be an array for service "%s" in "%s". Check your YAML syntax.', $id, $file));
}
foreach ($service['calls'] as $k => $call) {
if (!\is_array($call) && (!\is_string($k) || !$call instanceof TaggedValue)) {
throw new InvalidArgumentException(sprintf('Invalid method call for service "%s": expected map or array, "%s" given in "%s".', $id, $call instanceof TaggedValue ? '!'.$call->getTag() : get_debug_type($call), $file));
}
if (\is_string($k)) {
throw new InvalidArgumentException(sprintf('Invalid method call for service "%s", did you forgot a leading dash before "%s: ..." in "%s"?', $id, $k, $file));
}
if (isset($call['method'])) {
$method = $call['method'];
$args = $call['arguments'] ?? [];
$returnsClone = $call['returns_clone'] ?? false;
} else {
if (1 === \count($call) && \is_string(key($call))) {
$method = key($call);
$args = $call[$method];
if ($args instanceof TaggedValue) {
if ('returns_clone' !== $args->getTag()) {
throw new InvalidArgumentException(sprintf('Unsupported tag "!%s", did you mean "!returns_clone" for service "%s" in "%s"?', $args->getTag(), $id, $file));
}
$returnsClone = true;
$args = $args->getValue();
} else {
$returnsClone = false;
}
} elseif (empty($call[0])) {
throw new InvalidArgumentException(sprintf('Invalid call for service "%s": the method must be defined as the first index of an array or as the only key of a map in "%s".', $id, $file));
} else {
$method = $call[0];
$args = $call[1] ?? [];
$returnsClone = $call[2] ?? false;
}
}
if (!\is_array($args)) {
throw new InvalidArgumentException(sprintf('The second parameter for function call "%s" must be an array of its arguments for service "%s" in "%s". Check your YAML syntax.', $method, $id, $file));
}
$args = $this->resolveServices($args, $file);
$definition->addMethodCall($method, $args, $returnsClone);
}
}
$tags = isset($service['tags']) ? $service['tags'] : [];
if (!\is_array($tags)) {
throw new InvalidArgumentException(sprintf('Parameter "tags" must be an array for service "%s" in "%s". Check your YAML syntax.', $id, $file));
}
if (isset($defaults['tags'])) {
$tags = array_merge($tags, $defaults['tags']);
}
foreach ($tags as $tag) {
if (!\is_array($tag)) {
$tag = ['name' => $tag];
}
if (1 === \count($tag) && \is_array(current($tag))) {
$name = key($tag);
$tag = current($tag);
} else {
if (!isset($tag['name'])) {
throw new InvalidArgumentException(sprintf('A "tags" entry is missing a "name" key for service "%s" in "%s".', $id, $file));
}
$name = $tag['name'];
unset($tag['name']);
}
if (!\is_string($name) || '' === $name) {
throw new InvalidArgumentException(sprintf('The tag name for service "%s" in "%s" must be a non-empty string.', $id, $file));
}
foreach ($tag as $attribute => $value) {
if (!is_scalar($value) && null !== $value) {
throw new InvalidArgumentException(sprintf('A "tags" attribute must be of a scalar-type for service "%s", tag "%s", attribute "%s" in "%s". Check your YAML syntax.', $id, $name, $attribute, $file));
}
}
$definition->addTag($name, $tag);
}
if (null !== $decorates = $service['decorates'] ?? null) {
if ('' !== $decorates && '@' === $decorates[0]) {
throw new InvalidArgumentException(sprintf('The value of the "decorates" option for the "%s" service must be the id of the service without the "@" prefix (replace "%s" with "%s").', $id, $service['decorates'], substr($decorates, 1)));
}
$decorationOnInvalid = \array_key_exists('decoration_on_invalid', $service) ? $service['decoration_on_invalid'] : 'exception';
if ('exception' === $decorationOnInvalid) {
$invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE;
} elseif ('ignore' === $decorationOnInvalid) {
$invalidBehavior = ContainerInterface::IGNORE_ON_INVALID_REFERENCE;
} elseif (null === $decorationOnInvalid) {
$invalidBehavior = ContainerInterface::NULL_ON_INVALID_REFERENCE;
} elseif ('null' === $decorationOnInvalid) {
throw new InvalidArgumentException(sprintf('Invalid value "%s" for attribute "decoration_on_invalid" on service "%s". Did you mean null (without quotes) in "%s"?', $decorationOnInvalid, $id, $file));
} else {
throw new InvalidArgumentException(sprintf('Invalid value "%s" for attribute "decoration_on_invalid" on service "%s". Did you mean "exception", "ignore" or null in "%s"?', $decorationOnInvalid, $id, $file));
}
$renameId = isset($service['decoration_inner_name']) ? $service['decoration_inner_name'] : null;
$priority = isset($service['decoration_priority']) ? $service['decoration_priority'] : 0;
$definition->setDecoratedService($decorates, $renameId, $priority, $invalidBehavior);
}
if (isset($service['autowire'])) {
$definition->setAutowired($service['autowire']);
}
if (isset($defaults['bind']) || isset($service['bind'])) {
// deep clone, to avoid multiple process of the same instance in the passes
$bindings = isset($defaults['bind']) ? unserialize(serialize($defaults['bind'])) : [];
if (isset($service['bind'])) {
if (!\is_array($service['bind'])) {
throw new InvalidArgumentException(sprintf('Parameter "bind" must be an array for service "%s" in "%s". Check your YAML syntax.', $id, $file));
}
$bindings = array_merge($bindings, $this->resolveServices($service['bind'], $file));
$bindingType = $this->isLoadingInstanceof ? BoundArgument::INSTANCEOF_BINDING : BoundArgument::SERVICE_BINDING;
foreach ($bindings as $argument => $value) {
if (!$value instanceof BoundArgument) {
$bindings[$argument] = new BoundArgument($value, true, $bindingType, $file);
}
}
}
$definition->setBindings($bindings);
}
if (isset($service['autoconfigure'])) {
$definition->setAutoconfigured($service['autoconfigure']);
}
if (\array_key_exists('namespace', $service) && !\array_key_exists('resource', $service)) {
throw new InvalidArgumentException(sprintf('A "resource" attribute must be set when the "namespace" attribute is set for service "%s" in "%s". Check your YAML syntax.', $id, $file));
}
if ($return) {
if (\array_key_exists('resource', $service)) {
throw new InvalidArgumentException(sprintf('Invalid "resource" attribute found for service "%s" in "%s". Check your YAML syntax.', $id, $file));
}
return $definition;
}
if (\array_key_exists('resource', $service)) {
if (!\is_string($service['resource'])) {
throw new InvalidArgumentException(sprintf('A "resource" attribute must be of type string for service "%s" in "%s". Check your YAML syntax.', $id, $file));
}
$exclude = isset($service['exclude']) ? $service['exclude'] : null;
$namespace = isset($service['namespace']) ? $service['namespace'] : $id;
$this->registerClasses($definition, $namespace, $service['resource'], $exclude);
} else {
$this->setDefinition($id, $definition);
}
}
/**
* Parses a callable.
*
* @param string|array $callable A callable reference
*
* @throws InvalidArgumentException When errors occur
*
* @return string|array|Reference A parsed callable
*/
private function parseCallable($callable, string $parameter, string $id, string $file)
{
if (\is_string($callable)) {
if ('' !== $callable && '@' === $callable[0]) {
if (false === strpos($callable, ':')) {
return [$this->resolveServices($callable, $file), '__invoke'];
}
throw new InvalidArgumentException(sprintf('The value of the "%s" option for the "%s" service must be the id of the service without the "@" prefix (replace "%s" with "%s" in "%s").', $parameter, $id, $callable, substr($callable, 1), $file));
}
return $callable;
}
if (\is_array($callable)) {
if (isset($callable[0]) && isset($callable[1])) {
return [$this->resolveServices($callable[0], $file), $callable[1]];
}
if ('factory' === $parameter && isset($callable[1]) && null === $callable[0]) {
return $callable;
}
throw new InvalidArgumentException(sprintf('Parameter "%s" must contain an array with two elements for service "%s" in "%s". Check your YAML syntax.', $parameter, $id, $file));
}
throw new InvalidArgumentException(sprintf('Parameter "%s" must be a string or an array for service "%s" in "%s". Check your YAML syntax.', $parameter, $id, $file));
}
/**
* Loads a YAML file.
*
* @param string $file
*
* @return array The file content
*
* @throws InvalidArgumentException when the given file is not a local file or when it does not exist
*/
protected function loadFile($file)
{
if (!class_exists('Symfony\Component\Yaml\Parser')) {
throw new RuntimeException('Unable to load YAML config files as the Symfony Yaml Component is not installed.');
}
if (!stream_is_local($file)) {
throw new InvalidArgumentException(sprintf('This is not a local file "%s".', $file));
}
if (!is_file($file)) {
throw new InvalidArgumentException(sprintf('The file "%s" does not exist.', $file));
}
if (null === $this->yamlParser) {
$this->yamlParser = new YamlParser();
}
try {
$configuration = $this->yamlParser->parseFile($file, Yaml::PARSE_CONSTANT | Yaml::PARSE_CUSTOM_TAGS);
} catch (ParseException $e) {
throw new InvalidArgumentException(sprintf('The file "%s" does not contain valid YAML: ', $file).$e->getMessage(), 0, $e);
}
return $this->validate($configuration, $file);
}
/**
* Validates a YAML file.
*
* @throws InvalidArgumentException When service file is not valid
*/
private function validate($content, string $file): ?array
{
if (null === $content) {
return $content;
}
if (!\is_array($content)) {
throw new InvalidArgumentException(sprintf('The service file "%s" is not valid. It should contain an array. Check your YAML syntax.', $file));
}
foreach ($content as $namespace => $data) {
if (\in_array($namespace, ['imports', 'parameters', 'services'])) {
continue;
}
if (!$this->container->hasExtension($namespace)) {
$extensionNamespaces = array_filter(array_map(function (ExtensionInterface $ext) { return $ext->getAlias(); }, $this->container->getExtensions()));
throw new InvalidArgumentException(sprintf('There is no extension able to load the configuration for "%s" (in "%s"). Looked for namespace "%s", found "%s".', $namespace, $file, $namespace, $extensionNamespaces ? sprintf('"%s"', implode('", "', $extensionNamespaces)) : 'none'));
}
}
return $content;
}
/**
* Resolves services.
*
* @return array|string|Reference|ArgumentInterface
*/
private function resolveServices($value, string $file, bool $isParameter = false)
{
if ($value instanceof TaggedValue) {
$argument = $value->getValue();
if ('iterator' === $value->getTag()) {
if (!\is_array($argument)) {
throw new InvalidArgumentException(sprintf('"!iterator" tag only accepts sequences in "%s".', $file));
}
$argument = $this->resolveServices($argument, $file, $isParameter);
try {
return new IteratorArgument($argument);
} catch (InvalidArgumentException $e) {
throw new InvalidArgumentException(sprintf('"!iterator" tag only accepts arrays of "@service" references in "%s".', $file));
}
}
if ('service_locator' === $value->getTag()) {
if (!\is_array($argument)) {
throw new InvalidArgumentException(sprintf('"!service_locator" tag only accepts maps in "%s".', $file));
}
$argument = $this->resolveServices($argument, $file, $isParameter);
try {
return new ServiceLocatorArgument($argument);
} catch (InvalidArgumentException $e) {
throw new InvalidArgumentException(sprintf('"!service_locator" tag only accepts maps of "@service" references in "%s".', $file));
}
}
if (\in_array($value->getTag(), ['tagged', 'tagged_iterator', 'tagged_locator'], true)) {
$forLocator = 'tagged_locator' === $value->getTag();
if (\is_array($argument) && isset($argument['tag']) && $argument['tag']) {
if ($diff = array_diff(array_keys($argument), ['tag', 'index_by', 'default_index_method', 'default_priority_method'])) {
throw new InvalidArgumentException(sprintf('"!%s" tag contains unsupported key "%s"; supported ones are "tag", "index_by", "default_index_method", and "default_priority_method".', $value->getTag(), implode('", "', $diff)));
}
$argument = new TaggedIteratorArgument($argument['tag'], $argument['index_by'] ?? null, $argument['default_index_method'] ?? null, $forLocator, $argument['default_priority_method'] ?? null);
} elseif (\is_string($argument) && $argument) {
$argument = new TaggedIteratorArgument($argument, null, null, $forLocator);
} else {
throw new InvalidArgumentException(sprintf('"!%s" tags only accept a non empty string or an array with a key "tag" in "%s".', $value->getTag(), $file));
}
if ($forLocator) {
$argument = new ServiceLocatorArgument($argument);
}
return $argument;
}
if ('service' === $value->getTag()) {
if ($isParameter) {
throw new InvalidArgumentException(sprintf('Using an anonymous service in a parameter is not allowed in "%s".', $file));
}
$isLoadingInstanceof = $this->isLoadingInstanceof;
$this->isLoadingInstanceof = false;
$instanceof = $this->instanceof;
$this->instanceof = [];
$id = sprintf('.%d_%s', ++$this->anonymousServicesCount, preg_replace('/^.*\\\\/', '', isset($argument['class']) ? $argument['class'] : '').$this->anonymousServicesSuffix);
$this->parseDefinition($id, $argument, $file, []);
if (!$this->container->hasDefinition($id)) {
throw new InvalidArgumentException(sprintf('Creating an alias using the tag "!service" is not allowed in "%s".', $file));
}
$this->container->getDefinition($id);
$this->isLoadingInstanceof = $isLoadingInstanceof;
$this->instanceof = $instanceof;
return new Reference($id);
}
if ('abstract' === $value->getTag()) {
return new AbstractArgument($value->getValue());
}
throw new InvalidArgumentException(sprintf('Unsupported tag "!%s".', $value->getTag()));
}
if (\is_array($value)) {
foreach ($value as $k => $v) {
$value[$k] = $this->resolveServices($v, $file, $isParameter);
}
} elseif (\is_string($value) && 0 === strpos($value, '@=')) {
if (!class_exists(Expression::class)) {
throw new \LogicException(sprintf('The "@=" expression syntax cannot be used without the ExpressionLanguage component. Try running "composer require symfony/expression-language".'));
}
return new Expression(substr($value, 2));
} elseif (\is_string($value) && 0 === strpos($value, '@')) {
if (0 === strpos($value, '@@')) {
$value = substr($value, 1);
$invalidBehavior = null;
} elseif (0 === strpos($value, '@!')) {
$value = substr($value, 2);
$invalidBehavior = ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE;
} elseif (0 === strpos($value, '@?')) {
$value = substr($value, 2);
$invalidBehavior = ContainerInterface::IGNORE_ON_INVALID_REFERENCE;
} else {
$value = substr($value, 1);
$invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE;
}
if (null !== $invalidBehavior) {
$value = new Reference($value, $invalidBehavior);
}
}
return $value;
}
/**
* Loads from Extensions.
*/
private function loadFromExtensions(array $content)
{
foreach ($content as $namespace => $values) {
if (\in_array($namespace, ['imports', 'parameters', 'services'])) {
continue;
}
if (!\is_array($values) && null !== $values) {
$values = [];
}
$this->container->loadFromExtension($namespace, $values);
}
}
/**
* Checks the keywords used to define a service.
*/
private function checkDefinition(string $id, array $definition, string $file)
{
if ($this->isLoadingInstanceof) {
$keywords = self::$instanceofKeywords;
} elseif (isset($definition['resource']) || isset($definition['namespace'])) {
$keywords = self::$prototypeKeywords;
} else {
$keywords = self::$serviceKeywords;
}
foreach ($definition as $key => $value) {
if (!isset($keywords[$key])) {
throw new InvalidArgumentException(sprintf('The configuration key "%s" is unsupported for definition "%s" in "%s". Allowed configuration keys are "%s".', $key, $id, $file, implode('", "', $keywords)));
}
}
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader;
use Symfony\Component\Config\Exception\FileLocatorFileNotFoundException;
use Symfony\Component\Config\Exception\LoaderLoadException;
use Symfony\Component\Config\FileLocatorInterface;
use Symfony\Component\Config\Loader\FileLoader as BaseFileLoader;
use Symfony\Component\Config\Loader\Loader;
use Symfony\Component\Config\Resource\GlobResource;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
/**
* FileLoader is the abstract class used by all built-in loaders that are file based.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
abstract class FileLoader extends BaseFileLoader
{
public const ANONYMOUS_ID_REGEXP = '/^\.\d+_[^~]*+~[._a-zA-Z\d]{7}$/';
protected $container;
protected $isLoadingInstanceof = false;
protected $instanceof = [];
protected $interfaces = [];
protected $singlyImplemented = [];
protected $autoRegisterAliasesForSinglyImplementedInterfaces = true;
public function __construct(ContainerBuilder $container, FileLocatorInterface $locator)
{
$this->container = $container;
parent::__construct($locator);
}
/**
* {@inheritdoc}
*
* @param bool|string $ignoreErrors Whether errors should be ignored; pass "not_found" to ignore only when the loaded resource is not found
*/
public function import($resource, $type = null, $ignoreErrors = false, $sourceResource = null, $exclude = null)
{
$args = \func_get_args();
if ($ignoreNotFound = 'not_found' === $ignoreErrors) {
$args[2] = false;
} elseif (!\is_bool($ignoreErrors)) {
throw new \TypeError(sprintf('Invalid argument $ignoreErrors provided to "%s::import()": boolean or "not_found" expected, "%s" given.', static::class, get_debug_type($ignoreErrors)));
}
try {
parent::import(...$args);
} catch (LoaderLoadException $e) {
if (!$ignoreNotFound || !($prev = $e->getPrevious()) instanceof FileLocatorFileNotFoundException) {
throw $e;
}
foreach ($prev->getTrace() as $frame) {
if ('import' === ($frame['function'] ?? null) && is_a($frame['class'] ?? '', Loader::class, true)) {
break;
}
}
if (__FILE__ !== $frame['file']) {
throw $e;
}
}
}
/**
* Registers a set of classes as services using PSR-4 for discovery.
*
* @param Definition $prototype A definition to use as template
* @param string $namespace The namespace prefix of classes in the scanned directory
* @param string $resource The directory to look for classes, glob-patterns allowed
* @param string|string[]|null $exclude A globbed path of files to exclude or an array of globbed paths of files to exclude
*/
public function registerClasses(Definition $prototype, $namespace, $resource, $exclude = null)
{
if ('\\' !== substr($namespace, -1)) {
throw new InvalidArgumentException(sprintf('Namespace prefix must end with a "\\": "%s".', $namespace));
}
if (!preg_match('/^(?:[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+\\\\)++$/', $namespace)) {
throw new InvalidArgumentException(sprintf('Namespace is not a valid PSR-4 prefix: "%s".', $namespace));
}
$classes = $this->findClasses($namespace, $resource, (array) $exclude);
// prepare for deep cloning
$serializedPrototype = serialize($prototype);
foreach ($classes as $class => $errorMessage) {
if (interface_exists($class, false)) {
$this->interfaces[] = $class;
} else {
$this->setDefinition($class, $definition = unserialize($serializedPrototype));
if (null !== $errorMessage) {
$definition->addError($errorMessage);
continue;
}
foreach (class_implements($class, false) as $interface) {
$this->singlyImplemented[$interface] = ($this->singlyImplemented[$interface] ?? $class) !== $class ? false : $class;
}
}
}
if ($this->autoRegisterAliasesForSinglyImplementedInterfaces) {
$this->registerAliasesForSinglyImplementedInterfaces();
}
}
public function registerAliasesForSinglyImplementedInterfaces()
{
foreach ($this->interfaces as $interface) {
if (!empty($this->singlyImplemented[$interface]) && !$this->container->has($interface)) {
$this->container->setAlias($interface, $this->singlyImplemented[$interface]);
}
}
$this->interfaces = $this->singlyImplemented = [];
}
/**
* Registers a definition in the container with its instanceof-conditionals.
*
* @param string $id
*/
protected function setDefinition($id, Definition $definition)
{
$this->container->removeBindings($id);
if ($this->isLoadingInstanceof) {
if (!$definition instanceof ChildDefinition) {
throw new InvalidArgumentException(sprintf('Invalid type definition "%s": ChildDefinition expected, "%s" given.', $id, get_debug_type($definition)));
}
$this->instanceof[$id] = $definition;
} else {
$this->container->setDefinition($id, $definition->setInstanceofConditionals($this->instanceof));
}
}
private function findClasses(string $namespace, string $pattern, array $excludePatterns): array
{
$parameterBag = $this->container->getParameterBag();
$excludePaths = [];
$excludePrefix = null;
$excludePatterns = $parameterBag->unescapeValue($parameterBag->resolveValue($excludePatterns));
foreach ($excludePatterns as $excludePattern) {
foreach ($this->glob($excludePattern, true, $resource, true, true) as $path => $info) {
if (null === $excludePrefix) {
$excludePrefix = $resource->getPrefix();
}
// normalize Windows slashes
$excludePaths[str_replace('\\', '/', $path)] = true;
}
}
$pattern = $parameterBag->unescapeValue($parameterBag->resolveValue($pattern));
$classes = [];
$extRegexp = '/\\.php$/';
$prefixLen = null;
foreach ($this->glob($pattern, true, $resource, false, false, $excludePaths) as $path => $info) {
if (null === $prefixLen) {
$prefixLen = \strlen($resource->getPrefix());
if ($excludePrefix && 0 !== strpos($excludePrefix, $resource->getPrefix())) {
throw new InvalidArgumentException(sprintf('Invalid "exclude" pattern when importing classes for "%s": make sure your "exclude" pattern (%s) is a subset of the "resource" pattern (%s).', $namespace, $excludePattern, $pattern));
}
}
if (isset($excludePaths[str_replace('\\', '/', $path)])) {
continue;
}
if (!preg_match($extRegexp, $path, $m) || !$info->isReadable()) {
continue;
}
$class = $namespace.ltrim(str_replace('/', '\\', substr($path, $prefixLen, -\strlen($m[0]))), '\\');
if (!preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+(?:\\\\[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+)*+$/', $class)) {
continue;
}
try {
$r = $this->container->getReflectionClass($class);
} catch (\ReflectionException $e) {
$classes[$class] = $e->getMessage();
continue;
}
// check to make sure the expected class exists
if (!$r) {
throw new InvalidArgumentException(sprintf('Expected to find class "%s" in file "%s" while importing services from resource "%s", but it was not found! Check the namespace prefix used with the resource.', $class, $path, $pattern));
}
if ($r->isInstantiable() || $r->isInterface()) {
$classes[$class] = null;
}
}
// track only for new & removed files
if ($resource instanceof GlobResource) {
$this->container->addResource($resource);
} else {
foreach ($resource as $path) {
$this->container->fileExists($path, false);
}
}
return $classes;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
use Symfony\Component\DependencyInjection\Definition;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
class InstanceofConfigurator extends AbstractServiceConfigurator
{
const FACTORY = 'instanceof';
use Traits\AutowireTrait;
use Traits\BindTrait;
use Traits\CallTrait;
use Traits\ConfiguratorTrait;
use Traits\LazyTrait;
use Traits\PropertyTrait;
use Traits\PublicTrait;
use Traits\ShareTrait;
use Traits\TagTrait;
private $path;
public function __construct(ServicesConfigurator $parent, Definition $definition, string $id, string $path = null)
{
parent::__construct($parent, $definition, $id, []);
$this->path = $path;
}
/**
* Defines an instanceof-conditional to be applied to following service definitions.
*/
final public function instanceof(string $fqcn): self
{
return $this->parent->instanceof($fqcn);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
class ServiceConfigurator extends AbstractServiceConfigurator
{
const FACTORY = 'services';
use Traits\AbstractTrait;
use Traits\ArgumentTrait;
use Traits\AutoconfigureTrait;
use Traits\AutowireTrait;
use Traits\BindTrait;
use Traits\CallTrait;
use Traits\ClassTrait;
use Traits\ConfiguratorTrait;
use Traits\DecorateTrait;
use Traits\DeprecateTrait;
use Traits\FactoryTrait;
use Traits\FileTrait;
use Traits\LazyTrait;
use Traits\ParentTrait;
use Traits\PropertyTrait;
use Traits\PublicTrait;
use Traits\ShareTrait;
use Traits\SyntheticTrait;
use Traits\TagTrait;
private $container;
private $instanceof;
private $allowParent;
private $path;
public function __construct(ContainerBuilder $container, array $instanceof, bool $allowParent, ServicesConfigurator $parent, Definition $definition, $id, array $defaultTags, string $path = null)
{
$this->container = $container;
$this->instanceof = $instanceof;
$this->allowParent = $allowParent;
$this->path = $path;
parent::__construct($parent, $definition, $id, $defaultTags);
}
public function __destruct()
{
parent::__destruct();
$this->container->removeBindings($this->id);
$this->container->setDefinition($this->id, $this->definition->setInstanceofConditionals($this->instanceof));
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader\Configurator\Traits;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
trait TagTrait
{
/**
* Adds a tag for this definition.
*
* @return $this
*/
final public function tag(string $name, array $attributes = []): self
{
if ('' === $name) {
throw new InvalidArgumentException(sprintf('The tag name for service "%s" must be a non-empty string.', $this->id));
}
foreach ($attributes as $attribute => $value) {
if (!is_scalar($value) && null !== $value) {
throw new InvalidArgumentException(sprintf('A tag attribute must be of a scalar-type for service "%s", tag "%s", attribute "%s".', $this->id, $name, $attribute));
}
}
$this->definition->addTag($name, $attributes);
return $this;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader\Configurator\Traits;
use Symfony\Component\DependencyInjection\Argument\BoundArgument;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Loader\Configurator\DefaultsConfigurator;
use Symfony\Component\DependencyInjection\Loader\Configurator\InstanceofConfigurator;
use Symfony\Component\DependencyInjection\Reference;
trait BindTrait
{
/**
* Sets bindings.
*
* Bindings map $named or FQCN arguments to values that should be
* injected in the matching parameters (of the constructor, of methods
* called and of controller actions).
*
* @param string $nameOrFqcn A parameter name with its "$" prefix, or a FQCN
* @param mixed $valueOrRef The value or reference to bind
*
* @return $this
*/
final public function bind(string $nameOrFqcn, $valueOrRef): self
{
$valueOrRef = static::processValue($valueOrRef, true);
if (!preg_match('/^(?:(?:array|bool|float|int|string)[ \t]*+)?\$/', $nameOrFqcn) && !$valueOrRef instanceof Reference) {
throw new InvalidArgumentException(sprintf('Invalid binding for service "%s": named arguments must start with a "$", and FQCN must map to references. Neither applies to binding "%s".', $this->id, $nameOrFqcn));
}
$bindings = $this->definition->getBindings();
$type = $this instanceof DefaultsConfigurator ? BoundArgument::DEFAULTS_BINDING : ($this instanceof InstanceofConfigurator ? BoundArgument::INSTANCEOF_BINDING : BoundArgument::SERVICE_BINDING);
$bindings[$nameOrFqcn] = new BoundArgument($valueOrRef, true, $type, $this->path ?? null);
$this->definition->setBindings($bindings);
return $this;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader\Configurator\Traits;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
trait DecorateTrait
{
/**
* Sets the service that this service is decorating.
*
* @param string|null $id The decorated service id, use null to remove decoration
*
* @return $this
*
* @throws InvalidArgumentException in case the decorated service id and the new decorated service id are equals
*/
final public function decorate(?string $id, string $renamedId = null, int $priority = 0, int $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE): self
{
$this->definition->setDecoratedService($id, $renamedId, $priority, $invalidBehavior);
return $this;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader\Configurator\Traits;
trait SyntheticTrait
{
/**
* Sets whether this definition is synthetic, that is not constructed by the
* container, but dynamically injected.
*
* @return $this
*/
final public function synthetic(bool $synthetic = true): self
{
$this->definition->setSynthetic($synthetic);
return $this;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader\Configurator\Traits;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
trait ParentTrait
{
/**
* Sets the Definition to inherit from.
*
* @return $this
*
* @throws InvalidArgumentException when parent cannot be set
*/
final public function parent(string $parent): self
{
if (!$this->allowParent) {
throw new InvalidArgumentException(sprintf('A parent cannot be defined when either "_instanceof" or "_defaults" are also defined for service prototype "%s".', $this->id));
}
if ($this->definition instanceof ChildDefinition) {
$this->definition->setParent($parent);
} else {
// cast Definition to ChildDefinition
$definition = serialize($this->definition);
$definition = substr_replace($definition, '53', 2, 2);
$definition = substr_replace($definition, 'Child', 44, 0);
$definition = unserialize($definition);
$this->definition = $definition->setParent($parent);
}
return $this;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader\Configurator\Traits;
trait PropertyTrait
{
/**
* Sets a specific property.
*
* @return $this
*/
final public function property(string $name, $value): self
{
$this->definition->setProperty($name, static::processValue($value, true));
return $this;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader\Configurator\Traits;
trait AbstractTrait
{
/**
* Whether this definition is abstract, that means it merely serves as a
* template for other definitions.
*
* @return $this
*/
final public function abstract(bool $abstract = true): self
{
$this->definition->setAbstract($abstract);
return $this;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader\Configurator\Traits;
trait ArgumentTrait
{
/**
* Sets the arguments to pass to the service constructor/factory method.
*
* @return $this
*/
final public function args(array $arguments): self
{
$this->definition->setArguments(static::processValue($arguments, true));
return $this;
}
/**
* Sets one argument to pass to the service constructor/factory method.
*
* @param string|int $key
* @param mixed $value
*
* @return $this
*/
final public function arg($key, $value): self
{
$this->definition->setArgument($key, static::processValue($value, true));
return $this;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader\Configurator\Traits;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
trait AutoconfigureTrait
{
/**
* Sets whether or not instanceof conditionals should be prepended with a global set.
*
* @return $this
*
* @throws InvalidArgumentException when a parent is already set
*/
final public function autoconfigure(bool $autoconfigured = true): self
{
$this->definition->setAutoconfigured($autoconfigured);
return $this;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader\Configurator\Traits;
trait LazyTrait
{
/**
* Sets the lazy flag of this service.
*
* @param bool|string $lazy A FQCN to derivate the lazy proxy from or `true` to make it extend from the definition's class
*
* @return $this
*/
final public function lazy($lazy = true): self
{
$this->definition->setLazy((bool) $lazy);
if (\is_string($lazy)) {
$this->definition->addTag('proxy', ['interface' => $lazy]);
}
return $this;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader\Configurator\Traits;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
trait DeprecateTrait
{
/**
* Whether this definition is deprecated, that means it should not be called anymore.
*
* @param string $package The name of the composer package that is triggering the deprecation
* @param string $version The version of the package that introduced the deprecation
* @param string $message The deprecation message to use
*
* @return $this
*
* @throws InvalidArgumentException when the message template is invalid
*/
final public function deprecate(/* string $package, string $version, string $message */): self
{
$args = \func_get_args();
$package = $version = $message = '';
if (\func_num_args() < 3) {
trigger_deprecation('symfony/dependency-injection', '5.1', 'The signature of method "%s()" requires 3 arguments: "string $package, string $version, string $message", not defining them is deprecated.', __METHOD__);
$message = (string) ($args[0] ?? null);
} else {
$package = (string) $args[0];
$version = (string) $args[1];
$message = (string) $args[2];
}
$this->definition->setDeprecated($package, $version, $message);
return $this;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader\Configurator\Traits;
trait ClassTrait
{
/**
* Sets the service class.
*
* @return $this
*/
final public function class(?string $class): self
{
$this->definition->setClass($class);
return $this;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader\Configurator\Traits;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
trait CallTrait
{
/**
* Adds a method to call after service initialization.
*
* @param string $method The method name to call
* @param array $arguments An array of arguments to pass to the method call
* @param bool $returnsClone Whether the call returns the service instance or not
*
* @return $this
*
* @throws InvalidArgumentException on empty $method param
*/
final public function call(string $method, array $arguments = [], bool $returnsClone = false): self
{
$this->definition->addMethodCall($method, static::processValue($arguments, true), $returnsClone);
return $this;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader\Configurator\Traits;
trait PublicTrait
{
/**
* @return $this
*/
final public function public(): self
{
$this->definition->setPublic(true);
return $this;
}
/**
* @return $this
*/
final public function private(): self
{
$this->definition->setPublic(false);
return $this;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader\Configurator\Traits;
trait FileTrait
{
/**
* Sets a file to require before creating the service.
*
* @return $this
*/
final public function file(string $file): self
{
$this->definition->setFile($file);
return $this;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader\Configurator\Traits;
trait ShareTrait
{
/**
* Sets if the service must be shared or not.
*
* @return $this
*/
final public function share(bool $shared = true): self
{
$this->definition->setShared($shared);
return $this;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader\Configurator\Traits;
trait ConfiguratorTrait
{
/**
* Sets a configurator to call after the service is fully initialized.
*
* @param string|array $configurator A PHP callable reference
*
* @return $this
*/
final public function configurator($configurator): self
{
$this->definition->setConfigurator(static::processValue($configurator, true));
return $this;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader\Configurator\Traits;
trait AutowireTrait
{
/**
* Enables/disables autowiring.
*
* @return $this
*/
final public function autowire(bool $autowired = true): self
{
$this->definition->setAutowired($autowired);
return $this;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader\Configurator\Traits;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
trait FactoryTrait
{
/**
* Sets a factory.
*
* @param string|array $factory A PHP callable reference
*
* @return $this
*/
final public function factory($factory): self
{
if (\is_string($factory) && 1 === substr_count($factory, ':')) {
$factoryParts = explode(':', $factory);
throw new InvalidArgumentException(sprintf('Invalid factory "%s": the "service:method" notation is not available when using PHP-based DI configuration. Use "[service(\'%s\'), \'%s\']" instead.', $factory, $factoryParts[0], $factoryParts[1]));
}
$this->definition->setFactory(static::processValue($factory, true));
return $this;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
use Symfony\Component\DependencyInjection\Alias;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
use Symfony\Component\DependencyInjection\Loader\PhpFileLoader;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
class ServicesConfigurator extends AbstractConfigurator
{
const FACTORY = 'services';
private $defaults;
private $container;
private $loader;
private $instanceof;
private $path;
private $anonymousHash;
private $anonymousCount;
public function __construct(ContainerBuilder $container, PhpFileLoader $loader, array &$instanceof, string $path = null, int &$anonymousCount = 0)
{
$this->defaults = new Definition();
$this->container = $container;
$this->loader = $loader;
$this->instanceof = &$instanceof;
$this->path = $path;
$this->anonymousHash = ContainerBuilder::hash($path ?: mt_rand());
$this->anonymousCount = &$anonymousCount;
$instanceof = [];
}
/**
* Defines a set of defaults for following service definitions.
*/
final public function defaults(): DefaultsConfigurator
{
return new DefaultsConfigurator($this, $this->defaults = new Definition(), $this->path);
}
/**
* Defines an instanceof-conditional to be applied to following service definitions.
*/
final public function instanceof(string $fqcn): InstanceofConfigurator
{
$this->instanceof[$fqcn] = $definition = new ChildDefinition('');
return new InstanceofConfigurator($this, $definition, $fqcn, $this->path);
}
/**
* Registers a service.
*
* @param string|null $id The service id, or null to create an anonymous service
* @param string|null $class The class of the service, or null when $id is also the class name
*/
final public function set(?string $id, string $class = null): ServiceConfigurator
{
$defaults = $this->defaults;
$definition = new Definition();
if (null === $id) {
if (!$class) {
throw new \LogicException('Anonymous services must have a class name.');
}
$id = sprintf('.%d_%s', ++$this->anonymousCount, preg_replace('/^.*\\\\/', '', $class).'~'.$this->anonymousHash);
} elseif (!$defaults->isPublic() || !$defaults->isPrivate()) {
$definition->setPublic($defaults->isPublic() && !$defaults->isPrivate());
}
$definition->setAutowired($defaults->isAutowired());
$definition->setAutoconfigured($defaults->isAutoconfigured());
// deep clone, to avoid multiple process of the same instance in the passes
$definition->setBindings(unserialize(serialize($defaults->getBindings())));
$definition->setChanges([]);
$configurator = new ServiceConfigurator($this->container, $this->instanceof, true, $this, $definition, $id, $defaults->getTags(), $this->path);
return null !== $class ? $configurator->class($class) : $configurator;
}
/**
* Creates an alias.
*/
final public function alias(string $id, string $referencedId): AliasConfigurator
{
$ref = static::processValue($referencedId, true);
$alias = new Alias((string) $ref);
if (!$this->defaults->isPublic() || !$this->defaults->isPrivate()) {
$alias->setPublic($this->defaults->isPublic());
}
$this->container->setAlias($id, $alias);
return new AliasConfigurator($this, $alias);
}
/**
* Registers a PSR-4 namespace using a glob pattern.
*/
final public function load(string $namespace, string $resource): PrototypeConfigurator
{
return new PrototypeConfigurator($this, $this->loader, $this->defaults, $namespace, $resource, true);
}
/**
* Gets an already defined service definition.
*
* @throws ServiceNotFoundException if the service definition does not exist
*/
final public function get(string $id): ServiceConfigurator
{
$definition = $this->container->getDefinition($id);
return new ServiceConfigurator($this->container, $definition->getInstanceofConditionals(), true, $this, $definition, $id, []);
}
/**
* Registers a stack of decorator services.
*
* @param InlineServiceConfigurator[]|ReferenceConfigurator[] $services
*/
final public function stack(string $id, array $services): AliasConfigurator
{
foreach ($services as $i => $service) {
if ($service instanceof InlineServiceConfigurator) {
$definition = $service->definition->setInstanceofConditionals($this->instanceof);
$changes = $definition->getChanges();
$definition->setAutowired((isset($changes['autowired']) ? $definition : $this->defaults)->isAutowired());
$definition->setAutoconfigured((isset($changes['autoconfigured']) ? $definition : $this->defaults)->isAutoconfigured());
$definition->setBindings(array_merge($this->defaults->getBindings(), $definition->getBindings()));
$definition->setChanges($changes);
$services[$i] = $definition;
} elseif (!$service instanceof ReferenceConfigurator) {
throw new InvalidArgumentException(sprintf('"%s()" expects a list of definitions as returned by "%s()" or "%s()", "%s" given at index "%s" for service "%s".', __METHOD__, InlineServiceConfigurator::FACTORY, ReferenceConfigurator::FACTORY, $service instanceof AbstractConfigurator ? $service::FACTORY.'()' : get_debug_type($service), $i, $id));
}
}
$alias = $this->alias($id, '');
$alias->definition = $this->set($id)
->parent('')
->args($services)
->tag('container.stack')
->definition;
return $alias;
}
/**
* Registers a service.
*/
final public function __invoke(string $id, string $class = null): ServiceConfigurator
{
return $this->set($id, $class);
}
public function __destruct()
{
$this->loader->registerAliasesForSinglyImplementedInterfaces();
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
use Symfony\Component\DependencyInjection\Argument\AbstractArgument;
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Extension\ExtensionInterface;
use Symfony\Component\DependencyInjection\Loader\PhpFileLoader;
use Symfony\Component\ExpressionLanguage\Expression;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
class ContainerConfigurator extends AbstractConfigurator
{
const FACTORY = 'container';
private $container;
private $loader;
private $instanceof;
private $path;
private $file;
private $anonymousCount = 0;
public function __construct(ContainerBuilder $container, PhpFileLoader $loader, array &$instanceof, string $path, string $file)
{
$this->container = $container;
$this->loader = $loader;
$this->instanceof = &$instanceof;
$this->path = $path;
$this->file = $file;
}
final public function extension(string $namespace, array $config)
{
if (!$this->container->hasExtension($namespace)) {
$extensions = array_filter(array_map(function (ExtensionInterface $ext) { return $ext->getAlias(); }, $this->container->getExtensions()));
throw new InvalidArgumentException(sprintf('There is no extension able to load the configuration for "%s" (in "%s"). Looked for namespace "%s", found "%s".', $namespace, $this->file, $namespace, $extensions ? implode('", "', $extensions) : 'none'));
}
$this->container->loadFromExtension($namespace, static::processValue($config));
}
final public function import(string $resource, string $type = null, $ignoreErrors = false)
{
$this->loader->setCurrentDir(\dirname($this->path));
$this->loader->import($resource, $type, $ignoreErrors, $this->file);
}
final public function parameters(): ParametersConfigurator
{
return new ParametersConfigurator($this->container);
}
final public function services(): ServicesConfigurator
{
return new ServicesConfigurator($this->container, $this->loader, $this->instanceof, $this->path, $this->anonymousCount);
}
/**
* @return static
*/
final public function withPath(string $path): self
{
$clone = clone $this;
$clone->path = $clone->file = $path;
return $clone;
}
}
/**
* Creates a parameter.
*/
function param(string $name): string
{
return '%'.$name.'%';
}
/**
* Creates a service reference.
*
* @deprecated since Symfony 5.1, use service() instead.
*/
function ref(string $id): ReferenceConfigurator
{
trigger_deprecation('symfony/dependency-injection', '5.1', '"%s()" is deprecated, use "service()" instead.', __FUNCTION__);
return new ReferenceConfigurator($id);
}
/**
* Creates a reference to a service.
*/
function service(string $serviceId): ReferenceConfigurator
{
return new ReferenceConfigurator($serviceId);
}
/**
* Creates an inline service.
*
* @deprecated since Symfony 5.1, use inline_service() instead.
*/
function inline(string $class = null): InlineServiceConfigurator
{
trigger_deprecation('symfony/dependency-injection', '5.1', '"%s()" is deprecated, use "inline_service()" instead.', __FUNCTION__);
return new InlineServiceConfigurator(new Definition($class));
}
/**
* Creates an inline service.
*/
function inline_service(string $class = null): InlineServiceConfigurator
{
return new InlineServiceConfigurator(new Definition($class));
}
/**
* Creates a service locator.
*
* @param ReferenceConfigurator[] $values
*/
function service_locator(array $values): ServiceLocatorArgument
{
return new ServiceLocatorArgument(AbstractConfigurator::processValue($values, true));
}
/**
* Creates a lazy iterator.
*
* @param ReferenceConfigurator[] $values
*/
function iterator(array $values): IteratorArgument
{
return new IteratorArgument(AbstractConfigurator::processValue($values, true));
}
/**
* Creates a lazy iterator by tag name.
*/
function tagged_iterator(string $tag, string $indexAttribute = null, string $defaultIndexMethod = null, string $defaultPriorityMethod = null): TaggedIteratorArgument
{
return new TaggedIteratorArgument($tag, $indexAttribute, $defaultIndexMethod, false, $defaultPriorityMethod);
}
/**
* Creates a service locator by tag name.
*/
function tagged_locator(string $tag, string $indexAttribute = null, string $defaultIndexMethod = null): ServiceLocatorArgument
{
return new ServiceLocatorArgument(new TaggedIteratorArgument($tag, $indexAttribute, $defaultIndexMethod, true));
}
/**
* Creates an expression.
*/
function expr(string $expression): Expression
{
return new Expression($expression);
}
/**
* Creates an abstract argument.
*/
function abstract_arg(string $description): AbstractArgument
{
return new AbstractArgument($description);
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
class ParametersConfigurator extends AbstractConfigurator
{
const FACTORY = 'parameters';
private $container;
public function __construct(ContainerBuilder $container)
{
$this->container = $container;
}
/**
* Creates a parameter.
*
* @return $this
*/
final public function set(string $name, $value): self
{
$this->container->setParameter($name, static::processValue($value, true));
return $this;
}
/**
* Creates a parameter.
*
* @return $this
*/
final public function __invoke(string $name, $value): self
{
return $this->set($name, $value);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
use Symfony\Component\DependencyInjection\Alias;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
class AliasConfigurator extends AbstractServiceConfigurator
{
const FACTORY = 'alias';
use Traits\DeprecateTrait;
use Traits\PublicTrait;
public function __construct(ServicesConfigurator $parent, Alias $alias)
{
$this->parent = $parent;
$this->definition = $alias;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
use Symfony\Component\DependencyInjection\Argument\AbstractArgument;
use Symfony\Component\DependencyInjection\Argument\ArgumentInterface;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Parameter;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\ExpressionLanguage\Expression;
abstract class AbstractConfigurator
{
const FACTORY = 'unknown';
/**
* @var callable(mixed, bool $allowService)|null
*/
public static $valuePreProcessor;
/** @internal */
protected $definition;
public function __call(string $method, array $args)
{
if (method_exists($this, 'set'.$method)) {
return $this->{'set'.$method}(...$args);
}
throw new \BadMethodCallException(sprintf('Call to undefined method "%s::%s()".', static::class, $method));
}
/**
* Checks that a value is valid, optionally replacing Definition and Reference configurators by their configure value.
*
* @param mixed $value
* @param bool $allowServices whether Definition and Reference are allowed; by default, only scalars and arrays are
*
* @return mixed the value, optionally cast to a Definition/Reference
*/
public static function processValue($value, $allowServices = false)
{
if (\is_array($value)) {
foreach ($value as $k => $v) {
$value[$k] = static::processValue($v, $allowServices);
}
return self::$valuePreProcessor ? (self::$valuePreProcessor)($value, $allowServices) : $value;
}
if (self::$valuePreProcessor) {
$value = (self::$valuePreProcessor)($value, $allowServices);
}
if ($value instanceof ReferenceConfigurator) {
return new Reference($value->id, $value->invalidBehavior);
}
if ($value instanceof InlineServiceConfigurator) {
$def = $value->definition;
$value->definition = null;
return $def;
}
if ($value instanceof self) {
throw new InvalidArgumentException(sprintf('"%s()" can be used only at the root of service configuration files.', $value::FACTORY));
}
switch (true) {
case null === $value:
case is_scalar($value):
return $value;
case $value instanceof ArgumentInterface:
case $value instanceof Definition:
case $value instanceof Expression:
case $value instanceof Parameter:
case $value instanceof AbstractArgument:
case $value instanceof Reference:
if ($allowServices) {
return $value;
}
}
throw new InvalidArgumentException(sprintf('Cannot use values of type "%s" in service configuration files.', get_debug_type($value)));
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
class DefaultsConfigurator extends AbstractServiceConfigurator
{
const FACTORY = 'defaults';
use Traits\AutoconfigureTrait;
use Traits\AutowireTrait;
use Traits\BindTrait;
use Traits\PublicTrait;
private $path;
public function __construct(ServicesConfigurator $parent, Definition $definition, string $path = null)
{
parent::__construct($parent, $definition, null, []);
$this->path = $path;
}
/**
* Adds a tag for this definition.
*
* @return $this
*
* @throws InvalidArgumentException when an invalid tag name or attribute is provided
*/
final public function tag(string $name, array $attributes = []): self
{
if ('' === $name) {
throw new InvalidArgumentException('The tag name in "_defaults" must be a non-empty string.');
}
foreach ($attributes as $attribute => $value) {
if (null !== $value && !is_scalar($value)) {
throw new InvalidArgumentException(sprintf('Tag "%s", attribute "%s" in "_defaults" must be of a scalar-type.', $name, $attribute));
}
}
$this->definition->addTag($name, $attributes);
return $this;
}
/**
* Defines an instanceof-conditional to be applied to following service definitions.
*/
final public function instanceof(string $fqcn): InstanceofConfigurator
{
return $this->parent->instanceof($fqcn);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
abstract class AbstractServiceConfigurator extends AbstractConfigurator
{
protected $parent;
protected $id;
private $defaultTags = [];
public function __construct(ServicesConfigurator $parent, Definition $definition, string $id = null, array $defaultTags = [])
{
$this->parent = $parent;
$this->definition = $definition;
$this->id = $id;
$this->defaultTags = $defaultTags;
}
public function __destruct()
{
// default tags should be added last
foreach ($this->defaultTags as $name => $attributes) {
foreach ($attributes as $attributes) {
$this->definition->addTag($name, $attributes);
}
}
$this->defaultTags = [];
}
/**
* Registers a service.
*/
final public function set(?string $id, string $class = null): ServiceConfigurator
{
$this->__destruct();
return $this->parent->set($id, $class);
}
/**
* Creates an alias.
*/
final public function alias(string $id, string $referencedId): AliasConfigurator
{
$this->__destruct();
return $this->parent->alias($id, $referencedId);
}
/**
* Registers a PSR-4 namespace using a glob pattern.
*/
final public function load(string $namespace, string $resource): PrototypeConfigurator
{
$this->__destruct();
return $this->parent->load($namespace, $resource);
}
/**
* Gets an already defined service definition.
*
* @throws ServiceNotFoundException if the service definition does not exist
*/
final public function get(string $id): ServiceConfigurator
{
$this->__destruct();
return $this->parent->get($id);
}
/**
* Registers a stack of decorator services.
*
* @param InlineServiceConfigurator[]|ReferenceConfigurator[] $services
*/
final public function stack(string $id, array $services): AliasConfigurator
{
$this->__destruct();
return $this->parent->stack($id, $services);
}
/**
* Registers a service.
*/
final public function __invoke(string $id, string $class = null): ServiceConfigurator
{
$this->__destruct();
return $this->parent->set($id, $class);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
use Symfony\Component\DependencyInjection\Definition;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
class InlineServiceConfigurator extends AbstractConfigurator
{
const FACTORY = 'service';
use Traits\ArgumentTrait;
use Traits\AutowireTrait;
use Traits\BindTrait;
use Traits\CallTrait;
use Traits\ConfiguratorTrait;
use Traits\FactoryTrait;
use Traits\FileTrait;
use Traits\LazyTrait;
use Traits\ParentTrait;
use Traits\PropertyTrait;
use Traits\TagTrait;
private $id = '[inline]';
private $allowParent = true;
private $path = null;
public function __construct(Definition $definition)
{
$this->definition = $definition;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
class ReferenceConfigurator extends AbstractConfigurator
{
/** @internal */
protected $id;
/** @internal */
protected $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE;
public function __construct(string $id)
{
$this->id = $id;
}
/**
* @return $this
*/
final public function ignoreOnInvalid(): self
{
$this->invalidBehavior = ContainerInterface::IGNORE_ON_INVALID_REFERENCE;
return $this;
}
/**
* @return $this
*/
final public function nullOnInvalid(): self
{
$this->invalidBehavior = ContainerInterface::NULL_ON_INVALID_REFERENCE;
return $this;
}
/**
* @return $this
*/
final public function ignoreOnUninitialized(): self
{
$this->invalidBehavior = ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE;
return $this;
}
/**
* @return string
*/
public function __toString()
{
return $this->id;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Loader\PhpFileLoader;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
class PrototypeConfigurator extends AbstractServiceConfigurator
{
const FACTORY = 'load';
use Traits\AbstractTrait;
use Traits\ArgumentTrait;
use Traits\AutoconfigureTrait;
use Traits\AutowireTrait;
use Traits\BindTrait;
use Traits\CallTrait;
use Traits\ConfiguratorTrait;
use Traits\DeprecateTrait;
use Traits\FactoryTrait;
use Traits\LazyTrait;
use Traits\ParentTrait;
use Traits\PropertyTrait;
use Traits\PublicTrait;
use Traits\ShareTrait;
use Traits\TagTrait;
private $loader;
private $resource;
private $excludes;
private $allowParent;
public function __construct(ServicesConfigurator $parent, PhpFileLoader $loader, Definition $defaults, string $namespace, string $resource, bool $allowParent)
{
$definition = new Definition();
if (!$defaults->isPublic() || !$defaults->isPrivate()) {
$definition->setPublic($defaults->isPublic());
}
$definition->setAutowired($defaults->isAutowired());
$definition->setAutoconfigured($defaults->isAutoconfigured());
// deep clone, to avoid multiple process of the same instance in the passes
$definition->setBindings(unserialize(serialize($defaults->getBindings())));
$definition->setChanges([]);
$this->loader = $loader;
$this->resource = $resource;
$this->allowParent = $allowParent;
parent::__construct($parent, $definition, $namespace, $defaults->getTags());
}
public function __destruct()
{
parent::__destruct();
if ($this->loader) {
$this->loader->registerClasses($this->definition, $this->id, $this->resource, $this->excludes);
}
$this->loader = null;
}
/**
* Excludes files from registration using glob patterns.
*
* @param string[]|string $excludes
*
* @return $this
*/
final public function exclude($excludes): self
{
$this->excludes = (array) $excludes;
return $this;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader;
use Symfony\Component\Config\Util\XmlUtils;
use Symfony\Component\DependencyInjection\Alias;
use Symfony\Component\DependencyInjection\Argument\AbstractArgument;
use Symfony\Component\DependencyInjection\Argument\BoundArgument;
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
use Symfony\Component\DependencyInjection\Extension\ExtensionInterface;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\ExpressionLanguage\Expression;
/**
* XmlFileLoader loads XML files service definitions.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class XmlFileLoader extends FileLoader
{
const NS = 'http://symfony.com/schema/dic/services';
protected $autoRegisterAliasesForSinglyImplementedInterfaces = false;
/**
* {@inheritdoc}
*/
public function load($resource, string $type = null)
{
$path = $this->locator->locate($resource);
$xml = $this->parseFileToDOM($path);
$this->container->fileExists($path);
$defaults = $this->getServiceDefaults($xml, $path);
// anonymous services
$this->processAnonymousServices($xml, $path);
// imports
$this->parseImports($xml, $path);
// parameters
$this->parseParameters($xml, $path);
// extensions
$this->loadFromExtensions($xml);
// services
try {
$this->parseDefinitions($xml, $path, $defaults);
} finally {
$this->instanceof = [];
$this->registerAliasesForSinglyImplementedInterfaces();
}
}
/**
* {@inheritdoc}
*/
public function supports($resource, string $type = null)
{
if (!\is_string($resource)) {
return false;
}
if (null === $type && 'xml' === pathinfo($resource, \PATHINFO_EXTENSION)) {
return true;
}
return 'xml' === $type;
}
private function parseParameters(\DOMDocument $xml, string $file)
{
if ($parameters = $this->getChildren($xml->documentElement, 'parameters')) {
$this->container->getParameterBag()->add($this->getArgumentsAsPhp($parameters[0], 'parameter', $file));
}
}
private function parseImports(\DOMDocument $xml, string $file)
{
$xpath = new \DOMXPath($xml);
$xpath->registerNamespace('container', self::NS);
if (false === $imports = $xpath->query('//container:imports/container:import')) {
return;
}
$defaultDirectory = \dirname($file);
foreach ($imports as $import) {
$this->setCurrentDir($defaultDirectory);
$this->import($import->getAttribute('resource'), XmlUtils::phpize($import->getAttribute('type')) ?: null, XmlUtils::phpize($import->getAttribute('ignore-errors')) ?: false, $file);
}
}
private function parseDefinitions(\DOMDocument $xml, string $file, Definition $defaults)
{
$xpath = new \DOMXPath($xml);
$xpath->registerNamespace('container', self::NS);
if (false === $services = $xpath->query('//container:services/container:service|//container:services/container:prototype|//container:services/container:stack')) {
return;
}
$this->setCurrentDir(\dirname($file));
$this->instanceof = [];
$this->isLoadingInstanceof = true;
$instanceof = $xpath->query('//container:services/container:instanceof');
foreach ($instanceof as $service) {
$this->setDefinition((string) $service->getAttribute('id'), $this->parseDefinition($service, $file, new Definition()));
}
$this->isLoadingInstanceof = false;
foreach ($services as $service) {
if ('stack' === $service->tagName) {
$service->setAttribute('parent', '-');
$definition = $this->parseDefinition($service, $file, $defaults)
->setTags(array_merge_recursive(['container.stack' => [[]]], $defaults->getTags()))
;
$this->setDefinition($id = (string) $service->getAttribute('id'), $definition);
$stack = [];
foreach ($this->getChildren($service, 'service') as $k => $frame) {
$k = $frame->getAttribute('id') ?: $k;
$frame->setAttribute('id', $id.'" at index "'.$k);
if ($alias = $frame->getAttribute('alias')) {
$this->validateAlias($frame, $file);
$stack[$k] = new Reference($alias);
} else {
$stack[$k] = $this->parseDefinition($frame, $file, $defaults)
->setInstanceofConditionals($this->instanceof);
}
}
$definition->setArguments($stack);
} elseif (null !== $definition = $this->parseDefinition($service, $file, $defaults)) {
if ('prototype' === $service->tagName) {
$excludes = array_column($this->getChildren($service, 'exclude'), 'nodeValue');
if ($service->hasAttribute('exclude')) {
if (\count($excludes) > 0) {
throw new InvalidArgumentException('You cannot use both the attribute "exclude" and <exclude> tags at the same time.');
}
$excludes = [$service->getAttribute('exclude')];
}
$this->registerClasses($definition, (string) $service->getAttribute('namespace'), (string) $service->getAttribute('resource'), $excludes);
} else {
$this->setDefinition((string) $service->getAttribute('id'), $definition);
}
}
}
}
private function getServiceDefaults(\DOMDocument $xml, string $file): Definition
{
$xpath = new \DOMXPath($xml);
$xpath->registerNamespace('container', self::NS);
if (null === $defaultsNode = $xpath->query('//container:services/container:defaults')->item(0)) {
return new Definition();
}
$defaultsNode->setAttribute('id', '<defaults>');
return $this->parseDefinition($defaultsNode, $file, new Definition());
}
/**
* Parses an individual Definition.
*/
private function parseDefinition(\DOMElement $service, string $file, Definition $defaults): ?Definition
{
if ($alias = $service->getAttribute('alias')) {
$this->validateAlias($service, $file);
$this->container->setAlias((string) $service->getAttribute('id'), $alias = new Alias($alias));
if ($publicAttr = $service->getAttribute('public')) {
$alias->setPublic(XmlUtils::phpize($publicAttr));
} elseif ($defaults->getChanges()['public'] ?? false) {
$alias->setPublic($defaults->isPublic());
}
if ($deprecated = $this->getChildren($service, 'deprecated')) {
$message = $deprecated[0]->nodeValue ?: '';
$package = $deprecated[0]->getAttribute('package') ?: '';
$version = $deprecated[0]->getAttribute('version') ?: '';
if (!$deprecated[0]->hasAttribute('package')) {
trigger_deprecation('symfony/dependency-injection', '5.1', 'Not setting the attribute "package" of the node "deprecated" in "%s" is deprecated.', $file);
}
if (!$deprecated[0]->hasAttribute('version')) {
trigger_deprecation('symfony/dependency-injection', '5.1', 'Not setting the attribute "version" of the node "deprecated" in "%s" is deprecated.', $file);
}
$alias->setDeprecated($package, $version, $message);
}
return null;
}
if ($this->isLoadingInstanceof) {
$definition = new ChildDefinition('');
} elseif ($parent = $service->getAttribute('parent')) {
$definition = new ChildDefinition($parent);
} else {
$definition = new Definition();
}
if ($defaults->getChanges()['public'] ?? false) {
$definition->setPublic($defaults->isPublic());
}
$definition->setAutowired($defaults->isAutowired());
$definition->setAutoconfigured($defaults->isAutoconfigured());
$definition->setChanges([]);
foreach (['class', 'public', 'shared', 'synthetic', 'abstract'] as $key) {
if ($value = $service->getAttribute($key)) {
$method = 'set'.$key;
$definition->$method($value = XmlUtils::phpize($value));
}
}
if ($value = $service->getAttribute('lazy')) {
$definition->setLazy((bool) $value = XmlUtils::phpize($value));
if (\is_string($value)) {
$definition->addTag('proxy', ['interface' => $value]);
}
}
if ($value = $service->getAttribute('autowire')) {
$definition->setAutowired(XmlUtils::phpize($value));
}
if ($value = $service->getAttribute('autoconfigure')) {
$definition->setAutoconfigured(XmlUtils::phpize($value));
}
if ($files = $this->getChildren($service, 'file')) {
$definition->setFile($files[0]->nodeValue);
}
if ($deprecated = $this->getChildren($service, 'deprecated')) {
$message = $deprecated[0]->nodeValue ?: '';
$package = $deprecated[0]->getAttribute('package') ?: '';
$version = $deprecated[0]->getAttribute('version') ?: '';
if ('' === $package) {
trigger_deprecation('symfony/dependency-injection', '5.1', 'Not setting the attribute "package" of the node "deprecated" in "%s" is deprecated.', $file);
}
if ('' === $version) {
trigger_deprecation('symfony/dependency-injection', '5.1', 'Not setting the attribute "version" of the node "deprecated" in "%s" is deprecated.', $file);
}
$definition->setDeprecated($package, $version, $message);
}
$definition->setArguments($this->getArgumentsAsPhp($service, 'argument', $file, $definition instanceof ChildDefinition));
$definition->setProperties($this->getArgumentsAsPhp($service, 'property', $file));
if ($factories = $this->getChildren($service, 'factory')) {
$factory = $factories[0];
if ($function = $factory->getAttribute('function')) {
$definition->setFactory($function);
} else {
if ($childService = $factory->getAttribute('service')) {
$class = new Reference($childService, ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE);
} else {
$class = $factory->hasAttribute('class') ? $factory->getAttribute('class') : null;
}
$definition->setFactory([$class, $factory->getAttribute('method') ?: '__invoke']);
}
}
if ($configurators = $this->getChildren($service, 'configurator')) {
$configurator = $configurators[0];
if ($function = $configurator->getAttribute('function')) {
$definition->setConfigurator($function);
} else {
if ($childService = $configurator->getAttribute('service')) {
$class = new Reference($childService, ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE);
} else {
$class = $configurator->getAttribute('class');
}
$definition->setConfigurator([$class, $configurator->getAttribute('method') ?: '__invoke']);
}
}
foreach ($this->getChildren($service, 'call') as $call) {
$definition->addMethodCall($call->getAttribute('method'), $this->getArgumentsAsPhp($call, 'argument', $file), XmlUtils::phpize($call->getAttribute('returns-clone')));
}
$tags = $this->getChildren($service, 'tag');
foreach ($tags as $tag) {
$parameters = [];
$tagName = $tag->nodeValue;
foreach ($tag->attributes as $name => $node) {
if ('name' === $name && '' === $tagName) {
continue;
}
if (false !== strpos($name, '-') && false === strpos($name, '_') && !\array_key_exists($normalizedName = str_replace('-', '_', $name), $parameters)) {
$parameters[$normalizedName] = XmlUtils::phpize($node->nodeValue);
}
// keep not normalized key
$parameters[$name] = XmlUtils::phpize($node->nodeValue);
}
if ('' === $tagName && '' === $tagName = $tag->getAttribute('name')) {
throw new InvalidArgumentException(sprintf('The tag name for service "%s" in "%s" must be a non-empty string.', (string) $service->getAttribute('id'), $file));
}
$definition->addTag($tagName, $parameters);
}
$definition->setTags(array_merge_recursive($definition->getTags(), $defaults->getTags()));
$bindings = $this->getArgumentsAsPhp($service, 'bind', $file);
$bindingType = $this->isLoadingInstanceof ? BoundArgument::INSTANCEOF_BINDING : BoundArgument::SERVICE_BINDING;
foreach ($bindings as $argument => $value) {
$bindings[$argument] = new BoundArgument($value, true, $bindingType, $file);
}
// deep clone, to avoid multiple process of the same instance in the passes
$bindings = array_merge(unserialize(serialize($defaults->getBindings())), $bindings);
if ($bindings) {
$definition->setBindings($bindings);
}
if ($decorates = $service->getAttribute('decorates')) {
$decorationOnInvalid = $service->getAttribute('decoration-on-invalid') ?: 'exception';
if ('exception' === $decorationOnInvalid) {
$invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE;
} elseif ('ignore' === $decorationOnInvalid) {
$invalidBehavior = ContainerInterface::IGNORE_ON_INVALID_REFERENCE;
} elseif ('null' === $decorationOnInvalid) {
$invalidBehavior = ContainerInterface::NULL_ON_INVALID_REFERENCE;
} else {
throw new InvalidArgumentException(sprintf('Invalid value "%s" for attribute "decoration-on-invalid" on service "%s". Did you mean "exception", "ignore" or "null" in "%s"?', $decorationOnInvalid, (string) $service->getAttribute('id'), $file));
}
$renameId = $service->hasAttribute('decoration-inner-name') ? $service->getAttribute('decoration-inner-name') : null;
$priority = $service->hasAttribute('decoration-priority') ? $service->getAttribute('decoration-priority') : 0;
$definition->setDecoratedService($decorates, $renameId, $priority, $invalidBehavior);
}
return $definition;
}
/**
* Parses a XML file to a \DOMDocument.
*
* @throws InvalidArgumentException When loading of XML file returns error
*/
private function parseFileToDOM(string $file): \DOMDocument
{
try {
$dom = XmlUtils::loadFile($file, [$this, 'validateSchema']);
} catch (\InvalidArgumentException $e) {
throw new InvalidArgumentException(sprintf('Unable to parse file "%s": ', $file).$e->getMessage(), $e->getCode(), $e);
}
$this->validateExtensions($dom, $file);
return $dom;
}
/**
* Processes anonymous services.
*/
private function processAnonymousServices(\DOMDocument $xml, string $file)
{
$definitions = [];
$count = 0;
$suffix = '~'.ContainerBuilder::hash($file);
$xpath = new \DOMXPath($xml);
$xpath->registerNamespace('container', self::NS);
// anonymous services as arguments/properties
if (false !== $nodes = $xpath->query('//container:argument[@type="service"][not(@id)]|//container:property[@type="service"][not(@id)]|//container:bind[not(@id)]|//container:factory[not(@service)]|//container:configurator[not(@service)]')) {
foreach ($nodes as $node) {
if ($services = $this->getChildren($node, 'service')) {
// give it a unique name
$id = sprintf('.%d_%s', ++$count, preg_replace('/^.*\\\\/', '', $services[0]->getAttribute('class')).$suffix);
$node->setAttribute('id', $id);
$node->setAttribute('service', $id);
$definitions[$id] = [$services[0], $file];
$services[0]->setAttribute('id', $id);
// anonymous services are always private
// we could not use the constant false here, because of XML parsing
$services[0]->setAttribute('public', 'false');
}
}
}
// anonymous services "in the wild"
if (false !== $nodes = $xpath->query('//container:services/container:service[not(@id)]')) {
foreach ($nodes as $node) {
throw new InvalidArgumentException(sprintf('Top-level services must have "id" attribute, none found in "%s" at line %d.', $file, $node->getLineNo()));
}
}
// resolve definitions
uksort($definitions, 'strnatcmp');
foreach (array_reverse($definitions) as $id => [$domElement, $file]) {
if (null !== $definition = $this->parseDefinition($domElement, $file, new Definition())) {
$this->setDefinition($id, $definition);
}
}
}
private function getArgumentsAsPhp(\DOMElement $node, string $name, string $file, bool $isChildDefinition = false): array
{
$arguments = [];
foreach ($this->getChildren($node, $name) as $arg) {
if ($arg->hasAttribute('name')) {
$arg->setAttribute('key', $arg->getAttribute('name'));
}
// this is used by ChildDefinition to overwrite a specific
// argument of the parent definition
if ($arg->hasAttribute('index')) {
$key = ($isChildDefinition ? 'index_' : '').$arg->getAttribute('index');
} elseif (!$arg->hasAttribute('key')) {
// Append an empty argument, then fetch its key to overwrite it later
$arguments[] = null;
$keys = array_keys($arguments);
$key = array_pop($keys);
} else {
$key = $arg->getAttribute('key');
}
$onInvalid = $arg->getAttribute('on-invalid');
$invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE;
if ('ignore' == $onInvalid) {
$invalidBehavior = ContainerInterface::IGNORE_ON_INVALID_REFERENCE;
} elseif ('ignore_uninitialized' == $onInvalid) {
$invalidBehavior = ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE;
} elseif ('null' == $onInvalid) {
$invalidBehavior = ContainerInterface::NULL_ON_INVALID_REFERENCE;
}
switch ($arg->getAttribute('type')) {
case 'service':
if ('' === $arg->getAttribute('id')) {
throw new InvalidArgumentException(sprintf('Tag "<%s>" with type="service" has no or empty "id" attribute in "%s".', $name, $file));
}
$arguments[$key] = new Reference($arg->getAttribute('id'), $invalidBehavior);
break;
case 'expression':
if (!class_exists(Expression::class)) {
throw new \LogicException(sprintf('The type="expression" attribute cannot be used without the ExpressionLanguage component. Try running "composer require symfony/expression-language".'));
}
$arguments[$key] = new Expression($arg->nodeValue);
break;
case 'collection':
$arguments[$key] = $this->getArgumentsAsPhp($arg, $name, $file);
break;
case 'iterator':
$arg = $this->getArgumentsAsPhp($arg, $name, $file);
try {
$arguments[$key] = new IteratorArgument($arg);
} catch (InvalidArgumentException $e) {
throw new InvalidArgumentException(sprintf('Tag "<%s>" with type="iterator" only accepts collections of type="service" references in "%s".', $name, $file));
}
break;
case 'service_locator':
$arg = $this->getArgumentsAsPhp($arg, $name, $file);
try {
$arguments[$key] = new ServiceLocatorArgument($arg);
} catch (InvalidArgumentException $e) {
throw new InvalidArgumentException(sprintf('Tag "<%s>" with type="service_locator" only accepts maps of type="service" references in "%s".', $name, $file));
}
break;
case 'tagged':
case 'tagged_iterator':
case 'tagged_locator':
$type = $arg->getAttribute('type');
$forLocator = 'tagged_locator' === $type;
if (!$arg->getAttribute('tag')) {
throw new InvalidArgumentException(sprintf('Tag "<%s>" with type="%s" has no or empty "tag" attribute in "%s".', $name, $type, $file));
}
$arguments[$key] = new TaggedIteratorArgument($arg->getAttribute('tag'), $arg->getAttribute('index-by') ?: null, $arg->getAttribute('default-index-method') ?: null, $forLocator, $arg->getAttribute('default-priority-method') ?: null);
if ($forLocator) {
$arguments[$key] = new ServiceLocatorArgument($arguments[$key]);
}
break;
case 'binary':
if (false === $value = base64_decode($arg->nodeValue)) {
throw new InvalidArgumentException(sprintf('Tag "<%s>" with type="binary" is not a valid base64 encoded string.', $name));
}
$arguments[$key] = $value;
break;
case 'abstract':
$arguments[$key] = new AbstractArgument($arg->nodeValue);
break;
case 'string':
$arguments[$key] = $arg->nodeValue;
break;
case 'constant':
$arguments[$key] = \constant(trim($arg->nodeValue));
break;
default:
$arguments[$key] = XmlUtils::phpize($arg->nodeValue);
}
}
return $arguments;
}
/**
* Get child elements by name.
*
* @return \DOMElement[]
*/
private function getChildren(\DOMNode $node, string $name): array
{
$children = [];
foreach ($node->childNodes as $child) {
if ($child instanceof \DOMElement && $child->localName === $name && self::NS === $child->namespaceURI) {
$children[] = $child;
}
}
return $children;
}
/**
* Validates a documents XML schema.
*
* @return bool
*
* @throws RuntimeException When extension references a non-existent XSD file
*/
public function validateSchema(\DOMDocument $dom)
{
$schemaLocations = ['http://symfony.com/schema/dic/services' => str_replace('\\', '/', __DIR__.'/schema/dic/services/services-1.0.xsd')];
if ($element = $dom->documentElement->getAttributeNS('http://www.w3.org/2001/XMLSchema-instance', 'schemaLocation')) {
$items = preg_split('/\s+/', $element);
for ($i = 0, $nb = \count($items); $i < $nb; $i += 2) {
if (!$this->container->hasExtension($items[$i])) {
continue;
}
if (($extension = $this->container->getExtension($items[$i])) && false !== $extension->getXsdValidationBasePath()) {
$ns = $extension->getNamespace();
$path = str_replace([$ns, str_replace('http://', 'https://', $ns)], str_replace('\\', '/', $extension->getXsdValidationBasePath()).'/', $items[$i + 1]);
if (!is_file($path)) {
throw new RuntimeException(sprintf('Extension "%s" references a non-existent XSD file "%s".', get_debug_type($extension), $path));
}
$schemaLocations[$items[$i]] = $path;
}
}
}
$tmpfiles = [];
$imports = '';
foreach ($schemaLocations as $namespace => $location) {
$parts = explode('/', $location);
$locationstart = 'file:///';
if (0 === stripos($location, 'phar://')) {
$tmpfile = tempnam(sys_get_temp_dir(), 'symfony');
if ($tmpfile) {
copy($location, $tmpfile);
$tmpfiles[] = $tmpfile;
$parts = explode('/', str_replace('\\', '/', $tmpfile));
} else {
array_shift($parts);
$locationstart = 'phar:///';
}
}
$drive = '\\' === \DIRECTORY_SEPARATOR ? array_shift($parts).'/' : '';
$location = $locationstart.$drive.implode('/', array_map('rawurlencode', $parts));
$imports .= sprintf(' <xsd:import namespace="%s" schemaLocation="%s" />'."\n", $namespace, $location);
}
$source = <<<EOF
<?xml version="1.0" encoding="utf-8" ?>
<xsd:schema xmlns="http://symfony.com/schema"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://symfony.com/schema"
elementFormDefault="qualified">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
$imports
</xsd:schema>
EOF
;
if ($this->shouldEnableEntityLoader()) {
$disableEntities = libxml_disable_entity_loader(false);
$valid = @$dom->schemaValidateSource($source);
libxml_disable_entity_loader($disableEntities);
} else {
$valid = @$dom->schemaValidateSource($source);
}
foreach ($tmpfiles as $tmpfile) {
@unlink($tmpfile);
}
return $valid;
}
private function shouldEnableEntityLoader(): bool
{
// Version prior to 8.0 can be enabled without deprecation
if (\PHP_VERSION_ID < 80000) {
return true;
}
static $dom, $schema;
if (null === $dom) {
$dom = new \DOMDocument();
$dom->loadXML('<?xml version="1.0"?><test/>');
$tmpfile = tempnam(sys_get_temp_dir(), 'symfony');
register_shutdown_function(static function () use ($tmpfile) {
@unlink($tmpfile);
});
$schema = '<?xml version="1.0" encoding="utf-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<xsd:include schemaLocation="file:///'.str_replace('\\', '/', $tmpfile).'" />
</xsd:schema>';
file_put_contents($tmpfile, '<?xml version="1.0" encoding="utf-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<xsd:element name="test" type="testType" />
<xsd:complexType name="testType"/>
</xsd:schema>');
}
return !@$dom->schemaValidateSource($schema);
}
private function validateAlias(\DOMElement $alias, string $file)
{
foreach ($alias->attributes as $name => $node) {
if (!\in_array($name, ['alias', 'id', 'public'])) {
throw new InvalidArgumentException(sprintf('Invalid attribute "%s" defined for alias "%s" in "%s".', $name, $alias->getAttribute('id'), $file));
}
}
foreach ($alias->childNodes as $child) {
if (!$child instanceof \DOMElement || self::NS !== $child->namespaceURI) {
continue;
}
if (!\in_array($child->localName, ['deprecated'], true)) {
throw new InvalidArgumentException(sprintf('Invalid child element "%s" defined for alias "%s" in "%s".', $child->localName, $alias->getAttribute('id'), $file));
}
}
}
/**
* Validates an extension.
*
* @throws InvalidArgumentException When no extension is found corresponding to a tag
*/
private function validateExtensions(\DOMDocument $dom, string $file)
{
foreach ($dom->documentElement->childNodes as $node) {
if (!$node instanceof \DOMElement || 'http://symfony.com/schema/dic/services' === $node->namespaceURI) {
continue;
}
// can it be handled by an extension?
if (!$this->container->hasExtension($node->namespaceURI)) {
$extensionNamespaces = array_filter(array_map(function (ExtensionInterface $ext) { return $ext->getNamespace(); }, $this->container->getExtensions()));
throw new InvalidArgumentException(sprintf('There is no extension able to load the configuration for "%s" (in "%s"). Looked for namespace "%s", found "%s".', $node->tagName, $file, $node->namespaceURI, $extensionNamespaces ? implode('", "', $extensionNamespaces) : 'none'));
}
}
}
/**
* Loads from an extension.
*/
private function loadFromExtensions(\DOMDocument $xml)
{
foreach ($xml->documentElement->childNodes as $node) {
if (!$node instanceof \DOMElement || self::NS === $node->namespaceURI) {
continue;
}
$values = static::convertDomElementToArray($node);
if (!\is_array($values)) {
$values = [];
}
$this->container->loadFromExtension($node->namespaceURI, $values);
}
}
/**
* Converts a \DOMElement object to a PHP array.
*
* The following rules applies during the conversion:
*
* * Each tag is converted to a key value or an array
* if there is more than one "value"
*
* * The content of a tag is set under a "value" key (<foo>bar</foo>)
* if the tag also has some nested tags
*
* * The attributes are converted to keys (<foo foo="bar"/>)
*
* * The nested-tags are converted to keys (<foo><foo>bar</foo></foo>)
*
* @param \DOMElement $element A \DOMElement instance
*
* @return mixed
*/
public static function convertDomElementToArray(\DOMElement $element)
{
return XmlUtils::convertDomElementToArray($element);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader;
/**
* GlobFileLoader loads files from a glob pattern.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
class GlobFileLoader extends FileLoader
{
/**
* {@inheritdoc}
*/
public function load($resource, string $type = null)
{
foreach ($this->glob($resource, false, $globResource) as $path => $info) {
$this->import($path);
}
$this->container->addResource($globResource);
}
/**
* {@inheritdoc}
*/
public function supports($resource, string $type = null)
{
return 'glob' === $type;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader;
/**
* DirectoryLoader is a recursive loader to go through directories.
*
* @author Sebastien Lavoie <seb@wemakecustom.com>
*/
class DirectoryLoader extends FileLoader
{
/**
* {@inheritdoc}
*/
public function load($file, string $type = null)
{
$file = rtrim($file, '/');
$path = $this->locator->locate($file);
$this->container->fileExists($path, false);
foreach (scandir($path) as $dir) {
if ('.' !== $dir[0]) {
if (is_dir($path.'/'.$dir)) {
$dir .= '/'; // append / to allow recursion
}
$this->setCurrentDir($path);
$this->import($dir, null, false, $path);
}
}
}
/**
* {@inheritdoc}
*/
public function supports($resource, string $type = null)
{
if ('directory' === $type) {
return true;
}
return null === $type && \is_string($resource) && '/' === substr($resource, -1);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection;
use Psr\Cache\CacheItemPoolInterface;
use Symfony\Component\ExpressionLanguage\ExpressionLanguage as BaseExpressionLanguage;
if (!class_exists(BaseExpressionLanguage::class)) {
return;
}
/**
* Adds some function to the default ExpressionLanguage.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @see ExpressionLanguageProvider
*/
class ExpressionLanguage extends BaseExpressionLanguage
{
/**
* {@inheritdoc}
*/
public function __construct(CacheItemPoolInterface $cache = null, array $providers = [], callable $serviceCompiler = null)
{
// prepend the default provider to let users override it easily
array_unshift($providers, new ExpressionLanguageProvider($serviceCompiler));
parent::__construct($cache, $providers);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Config;
use Symfony\Component\Config\Resource\ResourceInterface;
/**
* Tracks container parameters.
*
* @author Maxime Steinhausser <maxime.steinhausser@gmail.com>
*
* @final
*/
class ContainerParametersResource implements ResourceInterface
{
private $parameters;
/**
* @param array $parameters The container parameters to track
*/
public function __construct(array $parameters)
{
$this->parameters = $parameters;
}
/**
* {@inheritdoc}
*/
public function __toString(): string
{
return 'container_parameters_'.md5(serialize($this->parameters));
}
/**
* @return array Tracked parameters
*/
public function getParameters(): array
{
return $this->parameters;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Config;
use Symfony\Component\Config\Resource\ResourceInterface;
use Symfony\Component\Config\ResourceCheckerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* @author Maxime Steinhausser <maxime.steinhausser@gmail.com>
*/
class ContainerParametersResourceChecker implements ResourceCheckerInterface
{
/** @var ContainerInterface */
private $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
/**
* {@inheritdoc}
*/
public function supports(ResourceInterface $metadata)
{
return $metadata instanceof ContainerParametersResource;
}
/**
* {@inheritdoc}
*/
public function isFresh(ResourceInterface $resource, int $timestamp)
{
foreach ($resource->getParameters() as $key => $value) {
if (!$this->container->hasParameter($key) || $this->container->getParameter($key) !== $value) {
return false;
}
}
return true;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection;
use Psr\Container\ContainerInterface as PsrContainerInterface;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException;
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
/**
* ContainerInterface is the interface implemented by service container classes.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
interface ContainerInterface extends PsrContainerInterface
{
const RUNTIME_EXCEPTION_ON_INVALID_REFERENCE = 0;
const EXCEPTION_ON_INVALID_REFERENCE = 1;
const NULL_ON_INVALID_REFERENCE = 2;
const IGNORE_ON_INVALID_REFERENCE = 3;
const IGNORE_ON_UNINITIALIZED_REFERENCE = 4;
/**
* Sets a service.
*/
public function set(string $id, ?object $service);
/**
* Gets a service.
*
* @param string $id The service identifier
* @param int $invalidBehavior The behavior when the service does not exist
*
* @return object|null The associated service
*
* @throws ServiceCircularReferenceException When a circular reference is detected
* @throws ServiceNotFoundException When the service is not defined
*
* @see Reference
*/
public function get($id, int $invalidBehavior = self::EXCEPTION_ON_INVALID_REFERENCE);
/**
* Returns true if the given service is defined.
*
* @param string $id The service identifier
*
* @return bool true if the service is defined, false otherwise
*/
public function has($id);
/**
* Check for whether or not a service has been initialized.
*
* @return bool true if the service has been initialized, false otherwise
*/
public function initialized(string $id);
/**
* Gets a parameter.
*
* @param string $name The parameter name
*
* @return mixed The parameter value
*
* @throws InvalidArgumentException if the parameter is not defined
*/
public function getParameter(string $name);
/**
* Checks if a parameter exists.
*
* @param string $name The parameter name
*
* @return bool The presence of parameter in container
*/
public function hasParameter(string $name);
/**
* Sets a parameter.
*
* @param string $name The parameter name
* @param mixed $value The parameter value
*/
public function setParameter(string $name, $value);
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
use Symfony\Component\DependencyInjection\Reference;
/**
* Checks that all references are pointing to a valid service.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class CheckExceptionOnInvalidReferenceBehaviorPass extends AbstractRecursivePass
{
private $serviceLocatorContextIds = [];
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container)
{
$this->serviceLocatorContextIds = [];
foreach ($container->findTaggedServiceIds('container.service_locator_context') as $id => $tags) {
$this->serviceLocatorContextIds[$id] = $tags[0]['id'];
$container->getDefinition($id)->clearTag('container.service_locator_context');
}
try {
return parent::process($container);
} finally {
$this->serviceLocatorContextIds = [];
}
}
protected function processValue($value, bool $isRoot = false)
{
if (!$value instanceof Reference) {
return parent::processValue($value, $isRoot);
}
if (ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE < $value->getInvalidBehavior() || $this->container->has($id = (string) $value)) {
return $value;
}
$currentId = $this->currentId;
$graph = $this->container->getCompiler()->getServiceReferenceGraph();
if (isset($this->serviceLocatorContextIds[$currentId])) {
$currentId = $this->serviceLocatorContextIds[$currentId];
$locator = $this->container->getDefinition($this->currentId)->getFactory()[0];
foreach ($locator->getArgument(0) as $k => $v) {
if ($v->getValues()[0] === $value) {
if ($k !== $id) {
$currentId = $k.'" in the container provided to "'.$currentId;
}
throw new ServiceNotFoundException($id, $currentId, null, $this->getAlternatives($id));
}
}
}
if ('.' === $currentId[0] && $graph->hasNode($currentId)) {
foreach ($graph->getNode($currentId)->getInEdges() as $edge) {
if (!$edge->getValue() instanceof Reference || ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE < $edge->getValue()->getInvalidBehavior()) {
continue;
}
$sourceId = $edge->getSourceNode()->getId();
if ('.' !== $sourceId[0]) {
$currentId = $sourceId;
break;
}
}
}
throw new ServiceNotFoundException($id, $currentId, null, $this->getAlternatives($id));
}
private function getAlternatives(string $id): array
{
$alternatives = [];
foreach ($this->container->getServiceIds() as $knownId) {
if ('' === $knownId || '.' === $knownId[0]) {
continue;
}
$lev = levenshtein($id, $knownId);
if ($lev <= \strlen($id) / 3 || false !== strpos($knownId, $id)) {
$alternatives[] = $knownId;
}
}
return $alternatives;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
use Symfony\Component\DependencyInjection\Reference;
/**
* Throws an exception for any Definitions that have errors and still exist.
*
* @author Ryan Weaver <ryan@knpuniversity.com>
*/
class DefinitionErrorExceptionPass extends AbstractRecursivePass
{
/**
* {@inheritdoc}
*/
protected function processValue($value, bool $isRoot = false)
{
if (!$value instanceof Definition || !$value->hasErrors()) {
return parent::processValue($value, $isRoot);
}
if ($isRoot && !$value->isPublic()) {
$graph = $this->container->getCompiler()->getServiceReferenceGraph();
$runtimeException = false;
foreach ($graph->getNode($this->currentId)->getInEdges() as $edge) {
if (!$edge->getValue() instanceof Reference || ContainerInterface::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE !== $edge->getValue()->getInvalidBehavior()) {
$runtimeException = false;
break;
}
$runtimeException = true;
}
if ($runtimeException) {
return parent::processValue($value, $isRoot);
}
}
// only show the first error so the user can focus on it
$errors = $value->getErrors();
$message = reset($errors);
throw new RuntimeException($message);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Argument\ArgumentInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
/**
* Propagate "container.hot_path" tags to referenced services.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
class ResolveHotPathPass extends AbstractRecursivePass
{
private $tagName;
private $resolvedIds = [];
public function __construct(string $tagName = 'container.hot_path')
{
$this->tagName = $tagName;
}
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container)
{
try {
parent::process($container);
$container->getDefinition('service_container')->clearTag($this->tagName);
} finally {
$this->resolvedIds = [];
}
}
/**
* {@inheritdoc}
*/
protected function processValue($value, bool $isRoot = false)
{
if ($value instanceof ArgumentInterface) {
return $value;
}
if ($value instanceof Definition && $isRoot) {
if ($value->isDeprecated()) {
return $value->clearTag($this->tagName);
}
$this->resolvedIds[$this->currentId] = true;
if (!$value->hasTag($this->tagName)) {
return $value;
}
}
if ($value instanceof Reference && ContainerBuilder::IGNORE_ON_UNINITIALIZED_REFERENCE !== $value->getInvalidBehavior() && $this->container->hasDefinition($id = (string) $value)) {
$definition = $this->container->getDefinition($id);
if ($definition->isDeprecated() || $definition->hasTag($this->tagName)) {
return $value;
}
$definition->addTag($this->tagName);
if (isset($this->resolvedIds[$id])) {
parent::processValue($definition, false);
}
return $value;
}
return parent::processValue($value, $isRoot);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
class ResolveClassPass implements CompilerPassInterface
{
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container)
{
foreach ($container->getDefinitions() as $id => $definition) {
if ($definition->isSynthetic() || null !== $definition->getClass()) {
continue;
}
if (preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+(?:\\\\[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+)++$/', $id)) {
if ($definition instanceof ChildDefinition && !class_exists($id)) {
throw new InvalidArgumentException(sprintf('Service definition "%s" has a parent but no class, and its name looks like a FQCN. Either the class is missing or you want to inherit it from the parent service. To resolve this ambiguity, please rename this service to a non-FQCN (e.g. using dots), or create the missing class.', $id));
}
$definition->setClass($id);
}
}
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\EnvVarProcessor;
use Symfony\Component\DependencyInjection\EnvVarProcessorInterface;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\ParameterBag\EnvPlaceholderParameterBag;
use Symfony\Component\DependencyInjection\Reference;
/**
* Creates the container.env_var_processors_locator service.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
class RegisterEnvVarProcessorsPass implements CompilerPassInterface
{
private static $allowedTypes = ['array', 'bool', 'float', 'int', 'string'];
public function process(ContainerBuilder $container)
{
$bag = $container->getParameterBag();
$types = [];
$processors = [];
foreach ($container->findTaggedServiceIds('container.env_var_processor') as $id => $tags) {
if (!$r = $container->getReflectionClass($class = $container->getDefinition($id)->getClass())) {
throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id));
} elseif (!$r->isSubclassOf(EnvVarProcessorInterface::class)) {
throw new InvalidArgumentException(sprintf('Service "%s" must implement interface "%s".', $id, EnvVarProcessorInterface::class));
}
foreach ($class::getProvidedTypes() as $prefix => $type) {
$processors[$prefix] = new Reference($id);
$types[$prefix] = self::validateProvidedTypes($type, $class);
}
}
if ($bag instanceof EnvPlaceholderParameterBag) {
foreach (EnvVarProcessor::getProvidedTypes() as $prefix => $type) {
if (!isset($types[$prefix])) {
$types[$prefix] = self::validateProvidedTypes($type, EnvVarProcessor::class);
}
}
$bag->setProvidedTypes($types);
}
if ($processors) {
$container->setAlias('container.env_var_processors_locator', (string) ServiceLocatorTagPass::register($container, $processors))
->setPublic(true)
;
}
}
private static function validateProvidedTypes(string $types, string $class): array
{
$types = explode('|', $types);
foreach ($types as $type) {
if (!\in_array($type, self::$allowedTypes)) {
throw new InvalidArgumentException(sprintf('Invalid type "%s" returned by "%s::getProvidedTypes()", expected one of "%s".', $type, $class, implode('", "', self::$allowedTypes)));
}
}
return $types;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Contracts\Service\Attribute\Required;
/**
* Looks for definitions with autowiring enabled and registers their corresponding "@required" methods as setters.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
class AutowireRequiredMethodsPass extends AbstractRecursivePass
{
/**
* {@inheritdoc}
*/
protected function processValue($value, bool $isRoot = false)
{
$value = parent::processValue($value, $isRoot);
if (!$value instanceof Definition || !$value->isAutowired() || $value->isAbstract() || !$value->getClass()) {
return $value;
}
if (!$reflectionClass = $this->container->getReflectionClass($value->getClass(), false)) {
return $value;
}
$alreadyCalledMethods = [];
$withers = [];
foreach ($value->getMethodCalls() as [$method]) {
$alreadyCalledMethods[strtolower($method)] = true;
}
foreach ($reflectionClass->getMethods() as $reflectionMethod) {
$r = $reflectionMethod;
if ($r->isConstructor() || isset($alreadyCalledMethods[strtolower($r->name)])) {
continue;
}
while (true) {
if (\PHP_VERSION_ID >= 80000 && $r->getAttributes(Required::class)) {
if ($this->isWither($r, $r->getDocComment() ?: '')) {
$withers[] = [$r->name, [], true];
} else {
$value->addMethodCall($r->name, []);
}
break;
}
if (false !== $doc = $r->getDocComment()) {
if (false !== stripos($doc, '@required') && preg_match('#(?:^/\*\*|\n\s*+\*)\s*+@required(?:\s|\*/$)#i', $doc)) {
if ($this->isWither($reflectionMethod, $doc)) {
$withers[] = [$reflectionMethod->name, [], true];
} else {
$value->addMethodCall($reflectionMethod->name, []);
}
break;
}
if (false === stripos($doc, '@inheritdoc') || !preg_match('#(?:^/\*\*|\n\s*+\*)\s*+(?:\{@inheritdoc\}|@inheritdoc)(?:\s|\*/$)#i', $doc)) {
break;
}
}
try {
$r = $r->getPrototype();
} catch (\ReflectionException $e) {
break; // method has no prototype
}
}
}
if ($withers) {
// Prepend withers to prevent creating circular loops
$setters = $value->getMethodCalls();
$value->setMethodCalls($withers);
foreach ($setters as $call) {
$value->addMethodCall($call[0], $call[1], $call[2] ?? false);
}
}
return $value;
}
private function isWither(\ReflectionMethod $reflectionMethod, string $doc): bool
{
$match = preg_match('#(?:^/\*\*|\n\s*+\*)\s*+@return\s++(static|\$this)[\s\*]#i', $doc, $matches);
if ($match && 'static' === $matches[1]) {
return true;
}
if ($match && '$this' === $matches[1]) {
return false;
}
$reflectionType = $reflectionMethod->hasReturnType() ? $reflectionMethod->getReturnType() : null;
return $reflectionType instanceof \ReflectionNamedType && 'static' === $reflectionType->getName();
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Exception\EnvParameterException;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
use Symfony\Component\DependencyInjection\Loader\FileLoader;
/**
* This pass validates each definition individually only taking the information
* into account which is contained in the definition itself.
*
* Later passes can rely on the following, and specifically do not need to
* perform these checks themselves:
*
* - non synthetic, non abstract services always have a class set
* - synthetic services are always public
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class CheckDefinitionValidityPass implements CompilerPassInterface
{
/**
* Processes the ContainerBuilder to validate the Definition.
*
* @throws RuntimeException When the Definition is invalid
*/
public function process(ContainerBuilder $container)
{
foreach ($container->getDefinitions() as $id => $definition) {
// synthetic service is public
if ($definition->isSynthetic() && !$definition->isPublic()) {
throw new RuntimeException(sprintf('A synthetic service ("%s") must be public.', $id));
}
// non-synthetic, non-abstract service has class
if (!$definition->isAbstract() && !$definition->isSynthetic() && !$definition->getClass() && (!$definition->getFactory() || !preg_match(FileLoader::ANONYMOUS_ID_REGEXP, $id))) {
if ($definition->getFactory()) {
throw new RuntimeException(sprintf('Please add the class to service "%s" even if it is constructed by a factory since we might need to add method calls based on compile-time checks.', $id));
}
if (class_exists($id) || interface_exists($id, false)) {
if (0 === strpos($id, '\\') && 1 < substr_count($id, '\\')) {
throw new RuntimeException(sprintf('The definition for "%s" has no class attribute, and appears to reference a class or interface. Please specify the class attribute explicitly or remove the leading backslash by renaming the service to "%s" to get rid of this error.', $id, substr($id, 1)));
}
throw new RuntimeException(sprintf('The definition for "%s" has no class attribute, and appears to reference a class or interface in the global namespace. Leaving out the "class" attribute is only allowed for namespaced classes. Please specify the class attribute explicitly to get rid of this error.', $id));
}
throw new RuntimeException(sprintf('The definition for "%s" has no class. If you intend to inject this service dynamically at runtime, please mark it as synthetic=true. If this is an abstract definition solely used by child definitions, please add abstract=true, otherwise specify a class to get rid of this error.', $id));
}
// tag attribute values must be scalars
foreach ($definition->getTags() as $name => $tags) {
foreach ($tags as $attributes) {
foreach ($attributes as $attribute => $value) {
if (!is_scalar($value) && null !== $value) {
throw new RuntimeException(sprintf('A "tags" attribute must be of a scalar-type for service "%s", tag "%s", attribute "%s".', $id, $name, $attribute));
}
}
}
}
if ($definition->isPublic() && !$definition->isPrivate()) {
$resolvedId = $container->resolveEnvPlaceholders($id, null, $usedEnvs);
if (null !== $usedEnvs) {
throw new EnvParameterException([$resolvedId], null, 'A service name ("%s") cannot contain dynamic values.');
}
}
}
foreach ($container->getAliases() as $id => $alias) {
if ($alias->isPublic() && !$alias->isPrivate()) {
$resolvedId = $container->resolveEnvPlaceholders($id, null, $usedEnvs);
if (null !== $usedEnvs) {
throw new EnvParameterException([$resolvedId], null, 'An alias name ("%s") cannot contain dynamic values.');
}
}
}
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Psr\Container\ContainerInterface as PsrContainerInterface;
use Symfony\Component\DependencyInjection\Argument\BoundArgument;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\TypedReference;
use Symfony\Contracts\Service\ServiceProviderInterface;
use Symfony\Contracts\Service\ServiceSubscriberInterface;
/**
* Compiler pass to register tagged services that require a service locator.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
class RegisterServiceSubscribersPass extends AbstractRecursivePass
{
protected function processValue($value, bool $isRoot = false)
{
if (!$value instanceof Definition || $value->isAbstract() || $value->isSynthetic() || !$value->hasTag('container.service_subscriber')) {
return parent::processValue($value, $isRoot);
}
$serviceMap = [];
$autowire = $value->isAutowired();
foreach ($value->getTag('container.service_subscriber') as $attributes) {
if (!$attributes) {
$autowire = true;
continue;
}
ksort($attributes);
if ([] !== array_diff(array_keys($attributes), ['id', 'key'])) {
throw new InvalidArgumentException(sprintf('The "container.service_subscriber" tag accepts only the "key" and "id" attributes, "%s" given for service "%s".', implode('", "', array_keys($attributes)), $this->currentId));
}
if (!\array_key_exists('id', $attributes)) {
throw new InvalidArgumentException(sprintf('Missing "id" attribute on "container.service_subscriber" tag with key="%s" for service "%s".', $attributes['key'], $this->currentId));
}
if (!\array_key_exists('key', $attributes)) {
$attributes['key'] = $attributes['id'];
}
if (isset($serviceMap[$attributes['key']])) {
continue;
}
$serviceMap[$attributes['key']] = new Reference($attributes['id']);
}
$class = $value->getClass();
if (!$r = $this->container->getReflectionClass($class)) {
throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $this->currentId));
}
if (!$r->isSubclassOf(ServiceSubscriberInterface::class)) {
throw new InvalidArgumentException(sprintf('Service "%s" must implement interface "%s".', $this->currentId, ServiceSubscriberInterface::class));
}
$class = $r->name;
$subscriberMap = [];
foreach ($class::getSubscribedServices() as $key => $type) {
if (!\is_string($type) || !preg_match('/^\??[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+(?:\\\\[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+)*+$/', $type)) {
throw new InvalidArgumentException(sprintf('"%s::getSubscribedServices()" must return valid PHP types for service "%s" key "%s", "%s" returned.', $class, $this->currentId, $key, \is_string($type) ? $type : get_debug_type($type)));
}
if ($optionalBehavior = '?' === $type[0]) {
$type = substr($type, 1);
$optionalBehavior = ContainerInterface::IGNORE_ON_INVALID_REFERENCE;
}
if (\is_int($name = $key)) {
$key = $type;
$name = null;
}
if (!isset($serviceMap[$key])) {
if (!$autowire) {
throw new InvalidArgumentException(sprintf('Service "%s" misses a "container.service_subscriber" tag with "key"/"id" attributes corresponding to entry "%s" as returned by "%s::getSubscribedServices()".', $this->currentId, $key, $class));
}
$serviceMap[$key] = new Reference($type);
}
if (false !== $i = strpos($name, '::get')) {
$name = lcfirst(substr($name, 5 + $i));
} elseif (false !== strpos($name, '::')) {
$name = null;
}
if (null !== $name && !$this->container->has($name) && !$this->container->has($type.' $'.$name)) {
$camelCaseName = lcfirst(str_replace(' ', '', ucwords(preg_replace('/[^a-zA-Z0-9\x7f-\xff]++/', ' ', $name))));
$name = $this->container->has($type.' $'.$camelCaseName) ? $camelCaseName : $name;
}
$subscriberMap[$key] = new TypedReference((string) $serviceMap[$key], $type, $optionalBehavior ?: ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, $name);
unset($serviceMap[$key]);
}
if ($serviceMap = array_keys($serviceMap)) {
$message = sprintf(1 < \count($serviceMap) ? 'keys "%s" do' : 'key "%s" does', str_replace('%', '%%', implode('", "', $serviceMap)));
throw new InvalidArgumentException(sprintf('Service %s not exist in the map returned by "%s::getSubscribedServices()" for service "%s".', $message, $class, $this->currentId));
}
$locatorRef = ServiceLocatorTagPass::register($this->container, $subscriberMap, $this->currentId);
$value->addTag('container.service_subscriber.locator', ['id' => (string) $locatorRef]);
$value->setBindings([
PsrContainerInterface::class => new BoundArgument($locatorRef, false),
ServiceProviderInterface::class => new BoundArgument($locatorRef, false),
] + $value->getBindings());
return parent::processValue($value);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Argument\ArgumentInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\LogicException;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
use Symfony\Component\DependencyInjection\ExpressionLanguage;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\ExpressionLanguage\Expression;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
abstract class AbstractRecursivePass implements CompilerPassInterface
{
/**
* @var ContainerBuilder
*/
protected $container;
protected $currentId;
private $processExpressions = false;
private $expressionLanguage;
private $inExpression = false;
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container)
{
$this->container = $container;
try {
$this->processValue($container->getDefinitions(), true);
} finally {
$this->container = null;
}
}
protected function enableExpressionProcessing()
{
$this->processExpressions = true;
}
protected function inExpression(bool $reset = true): bool
{
$inExpression = $this->inExpression;
if ($reset) {
$this->inExpression = false;
}
return $inExpression;
}
/**
* Processes a value found in a definition tree.
*
* @param mixed $value
*
* @return mixed The processed value
*/
protected function processValue($value, bool $isRoot = false)
{
if (\is_array($value)) {
foreach ($value as $k => $v) {
if ($isRoot) {
$this->currentId = $k;
}
if ($v !== $processedValue = $this->processValue($v, $isRoot)) {
$value[$k] = $processedValue;
}
}
} elseif ($value instanceof ArgumentInterface) {
$value->setValues($this->processValue($value->getValues()));
} elseif ($value instanceof Expression && $this->processExpressions) {
$this->getExpressionLanguage()->compile((string) $value, ['this' => 'container']);
} elseif ($value instanceof Definition) {
$value->setArguments($this->processValue($value->getArguments()));
$value->setProperties($this->processValue($value->getProperties()));
$value->setMethodCalls($this->processValue($value->getMethodCalls()));
$changes = $value->getChanges();
if (isset($changes['factory'])) {
$value->setFactory($this->processValue($value->getFactory()));
}
if (isset($changes['configurator'])) {
$value->setConfigurator($this->processValue($value->getConfigurator()));
}
}
return $value;
}
/**
* @return \ReflectionFunctionAbstract|null
*
* @throws RuntimeException
*/
protected function getConstructor(Definition $definition, bool $required)
{
if ($definition->isSynthetic()) {
return null;
}
if (\is_string($factory = $definition->getFactory())) {
if (!\function_exists($factory)) {
throw new RuntimeException(sprintf('Invalid service "%s": function "%s" does not exist.', $this->currentId, $factory));
}
$r = new \ReflectionFunction($factory);
if (false !== $r->getFileName() && file_exists($r->getFileName())) {
$this->container->fileExists($r->getFileName());
}
return $r;
}
if ($factory) {
[$class, $method] = $factory;
if ($class instanceof Reference) {
$class = $this->container->findDefinition((string) $class)->getClass();
} elseif ($class instanceof Definition) {
$class = $class->getClass();
} elseif (null === $class) {
$class = $definition->getClass();
}
if ('__construct' === $method) {
throw new RuntimeException(sprintf('Invalid service "%s": "__construct()" cannot be used as a factory method.', $this->currentId));
}
return $this->getReflectionMethod(new Definition($class), $method);
}
$class = $definition->getClass();
try {
if (!$r = $this->container->getReflectionClass($class)) {
throw new RuntimeException(sprintf('Invalid service "%s": class "%s" does not exist.', $this->currentId, $class));
}
} catch (\ReflectionException $e) {
throw new RuntimeException(sprintf('Invalid service "%s": ', $this->currentId).lcfirst($e->getMessage()));
}
if (!$r = $r->getConstructor()) {
if ($required) {
throw new RuntimeException(sprintf('Invalid service "%s": class%s has no constructor.', $this->currentId, sprintf($class !== $this->currentId ? ' "%s"' : '', $class)));
}
} elseif (!$r->isPublic()) {
throw new RuntimeException(sprintf('Invalid service "%s": ', $this->currentId).sprintf($class !== $this->currentId ? 'constructor of class "%s"' : 'its constructor', $class).' must be public.');
}
return $r;
}
/**
* @throws RuntimeException
*
* @return \ReflectionFunctionAbstract
*/
protected function getReflectionMethod(Definition $definition, string $method)
{
if ('__construct' === $method) {
return $this->getConstructor($definition, true);
}
if (!$class = $definition->getClass()) {
throw new RuntimeException(sprintf('Invalid service "%s": the class is not set.', $this->currentId));
}
if (!$r = $this->container->getReflectionClass($class)) {
throw new RuntimeException(sprintf('Invalid service "%s": class "%s" does not exist.', $this->currentId, $class));
}
if (!$r->hasMethod($method)) {
throw new RuntimeException(sprintf('Invalid service "%s": method "%s()" does not exist.', $this->currentId, $class !== $this->currentId ? $class.'::'.$method : $method));
}
$r = $r->getMethod($method);
if (!$r->isPublic()) {
throw new RuntimeException(sprintf('Invalid service "%s": method "%s()" must be public.', $this->currentId, $class !== $this->currentId ? $class.'::'.$method : $method));
}
return $r;
}
private function getExpressionLanguage(): ExpressionLanguage
{
if (null === $this->expressionLanguage) {
if (!class_exists(ExpressionLanguage::class)) {
throw new LogicException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed.');
}
$providers = $this->container->getExpressionLanguageProviders();
$this->expressionLanguage = new ExpressionLanguage(null, $providers, function (string $arg): string {
if ('""' === substr_replace($arg, '', 1, -1)) {
$id = stripcslashes(substr($arg, 1, -1));
$this->inExpression = true;
$arg = $this->processValue(new Reference($id));
$this->inExpression = false;
if (!$arg instanceof Reference) {
throw new RuntimeException(sprintf('"%s::processValue()" must return a Reference when processing an expression, "%s" returned for service("%s").', static::class, get_debug_type($arg), $id));
}
$arg = sprintf('"%s"', $arg);
}
return sprintf('$this->get(%s)', $arg);
});
}
return $this->expressionLanguage;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Exception\EnvParameterException;
/**
* This class is used to remove circular dependencies between individual passes.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class Compiler
{
private $passConfig;
private $log = [];
private $serviceReferenceGraph;
public function __construct()
{
$this->passConfig = new PassConfig();
$this->serviceReferenceGraph = new ServiceReferenceGraph();
}
/**
* Returns the PassConfig.
*
* @return PassConfig The PassConfig instance
*/
public function getPassConfig()
{
return $this->passConfig;
}
/**
* Returns the ServiceReferenceGraph.
*
* @return ServiceReferenceGraph The ServiceReferenceGraph instance
*/
public function getServiceReferenceGraph()
{
return $this->serviceReferenceGraph;
}
/**
* Adds a pass to the PassConfig.
*/
public function addPass(CompilerPassInterface $pass, string $type = PassConfig::TYPE_BEFORE_OPTIMIZATION, int $priority = 0)
{
$this->passConfig->addPass($pass, $type, $priority);
}
/**
* @final
*/
public function log(CompilerPassInterface $pass, string $message)
{
if (false !== strpos($message, "\n")) {
$message = str_replace("\n", "\n".\get_class($pass).': ', trim($message));
}
$this->log[] = \get_class($pass).': '.$message;
}
/**
* Returns the log.
*
* @return array Log array
*/
public function getLog()
{
return $this->log;
}
/**
* Run the Compiler and process all Passes.
*/
public function compile(ContainerBuilder $container)
{
try {
foreach ($this->passConfig->getPasses() as $pass) {
$pass->process($container);
}
} catch (\Exception $e) {
$usedEnvs = [];
$prev = $e;
do {
$msg = $prev->getMessage();
if ($msg !== $resolvedMsg = $container->resolveEnvPlaceholders($msg, null, $usedEnvs)) {
$r = new \ReflectionProperty($prev, 'message');
$r->setAccessible(true);
$r->setValue($prev, $resolvedMsg);
}
} while ($prev = $prev->getPrevious());
if ($usedEnvs) {
$e = new EnvParameterException($usedEnvs, $e);
}
throw $e;
} finally {
$this->getServiceReferenceGraph()->clear();
}
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException;
use Symfony\Component\DependencyInjection\Reference;
/**
* Replaces all references to aliases with references to the actual service.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class ResolveReferencesToAliasesPass extends AbstractRecursivePass
{
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container)
{
parent::process($container);
foreach ($container->getAliases() as $id => $alias) {
$aliasId = (string) $alias;
$this->currentId = $id;
if ($aliasId !== $defId = $this->getDefinitionId($aliasId, $container)) {
$container->setAlias($id, $defId)->setPublic($alias->isPublic());
}
}
}
/**
* {@inheritdoc}
*/
protected function processValue($value, bool $isRoot = false)
{
if (!$value instanceof Reference) {
return parent::processValue($value, $isRoot);
}
$defId = $this->getDefinitionId($id = (string) $value, $this->container);
return $defId !== $id ? new Reference($defId, $value->getInvalidBehavior()) : $value;
}
private function getDefinitionId(string $id, ContainerBuilder $container): string
{
if (!$container->hasAlias($id)) {
return $id;
}
$alias = $container->getAlias($id);
if ($alias->isDeprecated()) {
$deprecation = $alias->getDeprecation($id);
trigger_deprecation($deprecation['package'], $deprecation['version'], rtrim($deprecation['message'], '. ').'. It is being referenced by the "%s" '.($container->hasDefinition($this->currentId) ? 'service.' : 'alias.'), $this->currentId);
}
$seen = [];
do {
if (isset($seen[$id])) {
throw new ServiceCircularReferenceException($id, array_merge(array_keys($seen), [$id]));
}
$seen[$id] = true;
$id = (string) $container->getAlias($id);
} while ($container->hasAlias($id));
return $id;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Alias;
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\ServiceLocator;
/**
* Applies the "container.service_locator" tag by wrapping references into ServiceClosureArgument instances.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
final class ServiceLocatorTagPass extends AbstractRecursivePass
{
use PriorityTaggedServiceTrait;
protected function processValue($value, bool $isRoot = false)
{
if ($value instanceof ServiceLocatorArgument) {
if ($value->getTaggedIteratorArgument()) {
$value->setValues($this->findAndSortTaggedServices($value->getTaggedIteratorArgument(), $this->container));
}
return self::register($this->container, $value->getValues());
}
if (!$value instanceof Definition || !$value->hasTag('container.service_locator')) {
return parent::processValue($value, $isRoot);
}
if (!$value->getClass()) {
$value->setClass(ServiceLocator::class);
}
$arguments = $value->getArguments();
if (!isset($arguments[0]) || !\is_array($arguments[0])) {
throw new InvalidArgumentException(sprintf('Invalid definition for service "%s": an array of references is expected as first argument when the "container.service_locator" tag is set.', $this->currentId));
}
$i = 0;
foreach ($arguments[0] as $k => $v) {
if ($v instanceof ServiceClosureArgument) {
continue;
}
if (!$v instanceof Reference) {
throw new InvalidArgumentException(sprintf('Invalid definition for service "%s": an array of references is expected as first argument when the "container.service_locator" tag is set, "%s" found for key "%s".', $this->currentId, get_debug_type($v), $k));
}
if ($i === $k) {
unset($arguments[0][$k]);
$k = (string) $v;
++$i;
} elseif (\is_int($k)) {
$i = null;
}
$arguments[0][$k] = new ServiceClosureArgument($v);
}
ksort($arguments[0]);
$value->setArguments($arguments);
$id = '.service_locator.'.ContainerBuilder::hash($value);
if ($isRoot) {
if ($id !== $this->currentId) {
$this->container->setAlias($id, new Alias($this->currentId, false));
}
return $value;
}
$this->container->setDefinition($id, $value->setPublic(false));
return new Reference($id);
}
/**
* @param Reference[] $refMap
*/
public static function register(ContainerBuilder $container, array $refMap, string $callerId = null): Reference
{
foreach ($refMap as $id => $ref) {
if (!$ref instanceof Reference) {
throw new InvalidArgumentException(sprintf('Invalid service locator definition: only services can be referenced, "%s" found for key "%s". Inject parameter values using constructors instead.', get_debug_type($ref), $id));
}
$refMap[$id] = new ServiceClosureArgument($ref);
}
ksort($refMap);
$locator = (new Definition(ServiceLocator::class))
->addArgument($refMap)
->addTag('container.service_locator');
if (null !== $callerId && $container->hasDefinition($callerId)) {
$locator->setBindings($container->getDefinition($callerId)->getBindings());
}
if (!$container->hasDefinition($id = '.service_locator.'.ContainerBuilder::hash($locator))) {
$container->setDefinition($id, $locator);
}
if (null !== $callerId) {
$locatorId = $id;
// Locators are shared when they hold the exact same list of factories;
// to have them specialized per consumer service, we use a cloning factory
// to derivate customized instances from the prototype one.
$container->register($id .= '.'.$callerId, ServiceLocator::class)
->setFactory([new Reference($locatorId), 'withContext'])
->addTag('container.service_locator_context', ['id' => $callerId])
->addArgument($callerId)
->addArgument(new Reference('service_container'));
}
return new Reference($id);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\Config\Definition\BaseNode;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Exception\LogicException;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
use Symfony\Component\DependencyInjection\Extension\ConfigurationExtensionInterface;
use Symfony\Component\DependencyInjection\Extension\Extension;
use Symfony\Component\DependencyInjection\Extension\ExtensionInterface;
use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
use Symfony\Component\DependencyInjection\ParameterBag\EnvPlaceholderParameterBag;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
/**
* Merges extension configs into the container builder.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class MergeExtensionConfigurationPass implements CompilerPassInterface
{
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container)
{
$parameters = $container->getParameterBag()->all();
$definitions = $container->getDefinitions();
$aliases = $container->getAliases();
$exprLangProviders = $container->getExpressionLanguageProviders();
$configAvailable = class_exists(BaseNode::class);
foreach ($container->getExtensions() as $extension) {
if ($extension instanceof PrependExtensionInterface) {
$extension->prepend($container);
}
}
foreach ($container->getExtensions() as $name => $extension) {
if (!$config = $container->getExtensionConfig($name)) {
// this extension was not called
continue;
}
$resolvingBag = $container->getParameterBag();
if ($resolvingBag instanceof EnvPlaceholderParameterBag && $extension instanceof Extension) {
// create a dedicated bag so that we can track env vars per-extension
$resolvingBag = new MergeExtensionConfigurationParameterBag($resolvingBag);
if ($configAvailable) {
BaseNode::setPlaceholderUniquePrefix($resolvingBag->getEnvPlaceholderUniquePrefix());
}
}
$config = $resolvingBag->resolveValue($config);
try {
$tmpContainer = new MergeExtensionConfigurationContainerBuilder($extension, $resolvingBag);
$tmpContainer->setResourceTracking($container->isTrackingResources());
$tmpContainer->addObjectResource($extension);
if ($extension instanceof ConfigurationExtensionInterface && null !== $configuration = $extension->getConfiguration($config, $tmpContainer)) {
$tmpContainer->addObjectResource($configuration);
}
foreach ($exprLangProviders as $provider) {
$tmpContainer->addExpressionLanguageProvider($provider);
}
$extension->load($config, $tmpContainer);
} catch (\Exception $e) {
if ($resolvingBag instanceof MergeExtensionConfigurationParameterBag) {
$container->getParameterBag()->mergeEnvPlaceholders($resolvingBag);
}
if ($configAvailable) {
BaseNode::resetPlaceholders();
}
throw $e;
}
if ($resolvingBag instanceof MergeExtensionConfigurationParameterBag) {
// don't keep track of env vars that are *overridden* when configs are merged
$resolvingBag->freezeAfterProcessing($extension, $tmpContainer);
}
$container->merge($tmpContainer);
$container->getParameterBag()->add($parameters);
}
if ($configAvailable) {
BaseNode::resetPlaceholders();
}
$container->addDefinitions($definitions);
$container->addAliases($aliases);
}
}
/**
* @internal
*/
class MergeExtensionConfigurationParameterBag extends EnvPlaceholderParameterBag
{
private $processedEnvPlaceholders;
public function __construct(parent $parameterBag)
{
parent::__construct($parameterBag->all());
$this->mergeEnvPlaceholders($parameterBag);
}
public function freezeAfterProcessing(Extension $extension, ContainerBuilder $container)
{
if (!$config = $extension->getProcessedConfigs()) {
// Extension::processConfiguration() wasn't called, we cannot know how configs were merged
return;
}
$this->processedEnvPlaceholders = [];
// serialize config and container to catch env vars nested in object graphs
$config = serialize($config).serialize($container->getDefinitions()).serialize($container->getAliases()).serialize($container->getParameterBag()->all());
foreach (parent::getEnvPlaceholders() as $env => $placeholders) {
foreach ($placeholders as $placeholder) {
if (false !== stripos($config, $placeholder)) {
$this->processedEnvPlaceholders[$env] = $placeholders;
break;
}
}
}
}
/**
* {@inheritdoc}
*/
public function getEnvPlaceholders(): array
{
return null !== $this->processedEnvPlaceholders ? $this->processedEnvPlaceholders : parent::getEnvPlaceholders();
}
public function getUnusedEnvPlaceholders(): array
{
return null === $this->processedEnvPlaceholders ? [] : array_diff_key(parent::getEnvPlaceholders(), $this->processedEnvPlaceholders);
}
}
/**
* A container builder preventing using methods that wouldn't have any effect from extensions.
*
* @internal
*/
class MergeExtensionConfigurationContainerBuilder extends ContainerBuilder
{
private $extensionClass;
public function __construct(ExtensionInterface $extension, ParameterBagInterface $parameterBag = null)
{
parent::__construct($parameterBag);
$this->extensionClass = \get_class($extension);
}
/**
* {@inheritdoc}
*/
public function addCompilerPass(CompilerPassInterface $pass, string $type = PassConfig::TYPE_BEFORE_OPTIMIZATION, int $priority = 0): self
{
throw new LogicException(sprintf('You cannot add compiler pass "%s" from extension "%s". Compiler passes must be registered before the container is compiled.', get_debug_type($pass), $this->extensionClass));
}
/**
* {@inheritdoc}
*/
public function registerExtension(ExtensionInterface $extension)
{
throw new LogicException(sprintf('You cannot register extension "%s" from "%s". Extensions must be registered before the container is compiled.', get_debug_type($extension), $this->extensionClass));
}
/**
* {@inheritdoc}
*/
public function compile(bool $resolveEnvPlaceholders = false)
{
throw new LogicException(sprintf('Cannot compile the container in extension "%s".', $this->extensionClass));
}
/**
* {@inheritdoc}
*/
public function resolveEnvPlaceholders($value, $format = null, array &$usedEnvs = null)
{
if (true !== $format || !\is_string($value)) {
return parent::resolveEnvPlaceholders($value, $format, $usedEnvs);
}
$bag = $this->getParameterBag();
$value = $bag->resolveValue($value);
if (!$bag instanceof EnvPlaceholderParameterBag) {
return parent::resolveEnvPlaceholders($value, $format, $usedEnvs);
}
foreach ($bag->getEnvPlaceholders() as $env => $placeholders) {
if (false === strpos($env, ':')) {
continue;
}
foreach ($placeholders as $placeholder) {
if (false !== stripos($value, $placeholder)) {
throw new RuntimeException(sprintf('Using a cast in "env(%s)" is incompatible with resolution at compile time in "%s". The logic in the extension should be moved to a compiler pass, or an env parameter with no cast should be used instead.', $env, $this->extensionClass));
}
}
}
return parent::resolveEnvPlaceholders($value, $format, $usedEnvs);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\ContainerBuilder;
/**
* Remove private aliases from the container. They were only used to establish
* dependencies between services, and these dependencies have been resolved in
* one of the previous passes.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class RemovePrivateAliasesPass implements CompilerPassInterface
{
/**
* Removes private aliases from the ContainerBuilder.
*/
public function process(ContainerBuilder $container)
{
foreach ($container->getAliases() as $id => $alias) {
if ($alias->isPublic()) {
continue;
}
$container->removeAlias($id);
$container->log($this, sprintf('Removed service "%s"; reason: private alias.', $id));
}
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException;
/**
* Checks your services for circular references.
*
* References from method calls are ignored since we might be able to resolve
* these references depending on the order in which services are called.
*
* Circular reference from method calls will only be detected at run-time.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class CheckCircularReferencesPass implements CompilerPassInterface
{
private $currentPath;
private $checkedNodes;
/**
* Checks the ContainerBuilder object for circular references.
*/
public function process(ContainerBuilder $container)
{
$graph = $container->getCompiler()->getServiceReferenceGraph();
$this->checkedNodes = [];
foreach ($graph->getNodes() as $id => $node) {
$this->currentPath = [$id];
$this->checkOutEdges($node->getOutEdges());
}
}
/**
* Checks for circular references.
*
* @param ServiceReferenceGraphEdge[] $edges An array of Edges
*
* @throws ServiceCircularReferenceException when a circular reference is found
*/
private function checkOutEdges(array $edges)
{
foreach ($edges as $edge) {
$node = $edge->getDestNode();
$id = $node->getId();
if (empty($this->checkedNodes[$id])) {
// Don't check circular references for lazy edges
if (!$node->getValue() || (!$edge->isLazy() && !$edge->isWeak())) {
$searchKey = array_search($id, $this->currentPath);
$this->currentPath[] = $id;
if (false !== $searchKey) {
throw new ServiceCircularReferenceException($id, \array_slice($this->currentPath, $searchKey));
}
$this->checkOutEdges($node->getOutEdges());
}
$this->checkedNodes[$id] = true;
array_pop($this->currentPath);
}
}
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
/**
* Propagate the "container.no_preload" tag.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
class ResolveNoPreloadPass extends AbstractRecursivePass
{
private const DO_PRELOAD_TAG = '.container.do_preload';
private $tagName;
private $resolvedIds = [];
public function __construct(string $tagName = 'container.no_preload')
{
$this->tagName = $tagName;
}
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container)
{
$this->container = $container;
try {
foreach ($container->getDefinitions() as $id => $definition) {
if ($definition->isPublic() && !$definition->isPrivate() && !isset($this->resolvedIds[$id])) {
$this->resolvedIds[$id] = true;
$this->processValue($definition, true);
}
}
foreach ($container->getAliases() as $alias) {
if ($alias->isPublic() && !$alias->isPrivate() && !isset($this->resolvedIds[$id = (string) $alias]) && $container->hasDefinition($id)) {
$this->resolvedIds[$id] = true;
$this->processValue($container->getDefinition($id), true);
}
}
} finally {
$this->resolvedIds = [];
$this->container = null;
}
foreach ($container->getDefinitions() as $definition) {
if ($definition->hasTag(self::DO_PRELOAD_TAG)) {
$definition->clearTag(self::DO_PRELOAD_TAG);
} elseif (!$definition->isDeprecated() && !$definition->hasErrors()) {
$definition->addTag($this->tagName);
}
}
}
/**
* {@inheritdoc}
*/
protected function processValue($value, bool $isRoot = false)
{
if ($value instanceof Reference && ContainerBuilder::IGNORE_ON_UNINITIALIZED_REFERENCE !== $value->getInvalidBehavior() && $this->container->hasDefinition($id = (string) $value)) {
$definition = $this->container->getDefinition($id);
if (!isset($this->resolvedIds[$id]) && (!$definition->isPublic() || $definition->isPrivate())) {
$this->resolvedIds[$id] = true;
$this->processValue($definition, true);
}
return $value;
}
if (!$value instanceof Definition) {
return parent::processValue($value, $isRoot);
}
if ($value->hasTag($this->tagName) || $value->isDeprecated() || $value->hasErrors()) {
return $value;
}
if ($isRoot) {
$value->addTag(self::DO_PRELOAD_TAG);
}
return parent::processValue($value, $isRoot);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
/**
* Represents an edge in your service graph.
*
* Value is typically a reference.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class ServiceReferenceGraphEdge
{
private $sourceNode;
private $destNode;
private $value;
private $lazy;
private $weak;
private $byConstructor;
public function __construct(ServiceReferenceGraphNode $sourceNode, ServiceReferenceGraphNode $destNode, $value = null, bool $lazy = false, bool $weak = false, bool $byConstructor = false)
{
$this->sourceNode = $sourceNode;
$this->destNode = $destNode;
$this->value = $value;
$this->lazy = $lazy;
$this->weak = $weak;
$this->byConstructor = $byConstructor;
}
/**
* Returns the value of the edge.
*
* @return mixed
*/
public function getValue()
{
return $this->value;
}
/**
* Returns the source node.
*
* @return ServiceReferenceGraphNode
*/
public function getSourceNode()
{
return $this->sourceNode;
}
/**
* Returns the destination node.
*
* @return ServiceReferenceGraphNode
*/
public function getDestNode()
{
return $this->destNode;
}
/**
* Returns true if the edge is lazy, meaning it's a dependency not requiring direct instantiation.
*
* @return bool
*/
public function isLazy()
{
return $this->lazy;
}
/**
* Returns true if the edge is weak, meaning it shouldn't prevent removing the target service.
*
* @return bool
*/
public function isWeak()
{
return $this->weak;
}
/**
* Returns true if the edge links with a constructor argument.
*
* @return bool
*/
public function isReferencedByConstructor()
{
return $this->byConstructor;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\Config\Resource\ClassExistenceResource;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\AutowiringFailedException;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
use Symfony\Component\DependencyInjection\LazyProxy\ProxyHelper;
use Symfony\Component\DependencyInjection\TypedReference;
/**
* Inspects existing service definitions and wires the autowired ones using the type hints of their classes.
*
* @author Kévin Dunglas <dunglas@gmail.com>
* @author Nicolas Grekas <p@tchwork.com>
*/
class AutowirePass extends AbstractRecursivePass
{
private $types;
private $ambiguousServiceTypes;
private $lastFailure;
private $throwOnAutowiringException;
private $decoratedClass;
private $decoratedId;
private $methodCalls;
private $getPreviousValue;
private $decoratedMethodIndex;
private $decoratedMethodArgumentIndex;
private $typesClone;
public function __construct(bool $throwOnAutowireException = true)
{
$this->throwOnAutowiringException = $throwOnAutowireException;
}
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container)
{
try {
$this->typesClone = clone $this;
parent::process($container);
} finally {
$this->decoratedClass = null;
$this->decoratedId = null;
$this->methodCalls = null;
$this->getPreviousValue = null;
$this->decoratedMethodIndex = null;
$this->decoratedMethodArgumentIndex = null;
$this->typesClone = null;
}
}
/**
* {@inheritdoc}
*/
protected function processValue($value, bool $isRoot = false)
{
try {
return $this->doProcessValue($value, $isRoot);
} catch (AutowiringFailedException $e) {
if ($this->throwOnAutowiringException) {
throw $e;
}
$this->container->getDefinition($this->currentId)->addError($e->getMessageCallback() ?? $e->getMessage());
return parent::processValue($value, $isRoot);
}
}
/**
* @return mixed
*/
private function doProcessValue($value, bool $isRoot = false)
{
if ($value instanceof TypedReference) {
if ($ref = $this->getAutowiredReference($value)) {
return $ref;
}
if (ContainerBuilder::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE === $value->getInvalidBehavior()) {
$message = $this->createTypeNotFoundMessageCallback($value, 'it');
// since the error message varies by referenced id and $this->currentId, so should the id of the dummy errored definition
$this->container->register($id = sprintf('.errored.%s.%s', $this->currentId, (string) $value), $value->getType())
->addError($message);
return new TypedReference($id, $value->getType(), $value->getInvalidBehavior(), $value->getName());
}
}
$value = parent::processValue($value, $isRoot);
if (!$value instanceof Definition || !$value->isAutowired() || $value->isAbstract() || !$value->getClass()) {
return $value;
}
if (!$reflectionClass = $this->container->getReflectionClass($value->getClass(), false)) {
$this->container->log($this, sprintf('Skipping service "%s": Class or interface "%s" cannot be loaded.', $this->currentId, $value->getClass()));
return $value;
}
$this->methodCalls = $value->getMethodCalls();
try {
$constructor = $this->getConstructor($value, false);
} catch (RuntimeException $e) {
throw new AutowiringFailedException($this->currentId, $e->getMessage(), 0, $e);
}
if ($constructor) {
array_unshift($this->methodCalls, [$constructor, $value->getArguments()]);
}
$this->methodCalls = $this->autowireCalls($reflectionClass, $isRoot);
if ($constructor) {
[, $arguments] = array_shift($this->methodCalls);
if ($arguments !== $value->getArguments()) {
$value->setArguments($arguments);
}
}
if ($this->methodCalls !== $value->getMethodCalls()) {
$value->setMethodCalls($this->methodCalls);
}
return $value;
}
private function autowireCalls(\ReflectionClass $reflectionClass, bool $isRoot): array
{
$this->decoratedId = null;
$this->decoratedClass = null;
$this->getPreviousValue = null;
if ($isRoot && ($definition = $this->container->getDefinition($this->currentId)) && $this->container->has($this->decoratedId = $definition->innerServiceId)) {
$this->decoratedClass = $this->container->findDefinition($this->decoratedId)->getClass();
}
foreach ($this->methodCalls as $i => $call) {
$this->decoratedMethodIndex = $i;
[$method, $arguments] = $call;
if ($method instanceof \ReflectionFunctionAbstract) {
$reflectionMethod = $method;
} else {
$definition = new Definition($reflectionClass->name);
try {
$reflectionMethod = $this->getReflectionMethod($definition, $method);
} catch (RuntimeException $e) {
if ($definition->getFactory()) {
continue;
}
throw $e;
}
}
$arguments = $this->autowireMethod($reflectionMethod, $arguments);
if ($arguments !== $call[1]) {
$this->methodCalls[$i][1] = $arguments;
}
}
return $this->methodCalls;
}
/**
* Autowires the constructor or a method.
*
* @return array The autowired arguments
*
* @throws AutowiringFailedException
*/
private function autowireMethod(\ReflectionFunctionAbstract $reflectionMethod, array $arguments): array
{
$class = $reflectionMethod instanceof \ReflectionMethod ? $reflectionMethod->class : $this->currentId;
$method = $reflectionMethod->name;
$parameters = $reflectionMethod->getParameters();
if ($reflectionMethod->isVariadic()) {
array_pop($parameters);
}
foreach ($parameters as $index => $parameter) {
if (\array_key_exists($index, $arguments) && '' !== $arguments[$index]) {
continue;
}
$type = ProxyHelper::getTypeHint($reflectionMethod, $parameter, true);
if (!$type) {
if (isset($arguments[$index])) {
continue;
}
// no default value? Then fail
if (!$parameter->isDefaultValueAvailable()) {
// For core classes, isDefaultValueAvailable() can
// be false when isOptional() returns true. If the
// argument *is* optional, allow it to be missing
if ($parameter->isOptional()) {
continue;
}
$type = ProxyHelper::getTypeHint($reflectionMethod, $parameter, false);
$type = $type ? sprintf('is type-hinted "%s"', ltrim($type, '\\')) : 'has no type-hint';
throw new AutowiringFailedException($this->currentId, sprintf('Cannot autowire service "%s": argument "$%s" of method "%s()" %s, you should configure its value explicitly.', $this->currentId, $parameter->name, $class !== $this->currentId ? $class.'::'.$method : $method, $type));
}
// specifically pass the default value
$arguments[$index] = $parameter->getDefaultValue();
continue;
}
$getValue = function () use ($type, $parameter, $class, $method) {
if (!$value = $this->getAutowiredReference($ref = new TypedReference($type, $type, ContainerBuilder::EXCEPTION_ON_INVALID_REFERENCE, $parameter->name))) {
$failureMessage = $this->createTypeNotFoundMessageCallback($ref, sprintf('argument "$%s" of method "%s()"', $parameter->name, $class !== $this->currentId ? $class.'::'.$method : $method));
if ($parameter->isDefaultValueAvailable()) {
$value = $parameter->getDefaultValue();
} elseif (!$parameter->allowsNull()) {
throw new AutowiringFailedException($this->currentId, $failureMessage);
}
}
return $value;
};
if ($this->decoratedClass && $isDecorated = is_a($this->decoratedClass, $type, true)) {
if ($this->getPreviousValue) {
// The inner service is injected only if there is only 1 argument matching the type of the decorated class
// across all arguments of all autowired methods.
// If a second matching argument is found, the default behavior is restored.
$getPreviousValue = $this->getPreviousValue;
$this->methodCalls[$this->decoratedMethodIndex][1][$this->decoratedMethodArgumentIndex] = $getPreviousValue();
$this->decoratedClass = null; // Prevent further checks
} else {
$arguments[$index] = new TypedReference($this->decoratedId, $this->decoratedClass);
$this->getPreviousValue = $getValue;
$this->decoratedMethodArgumentIndex = $index;
continue;
}
}
$arguments[$index] = $getValue();
}
if ($parameters && !isset($arguments[++$index])) {
while (0 <= --$index) {
$parameter = $parameters[$index];
if (!$parameter->isDefaultValueAvailable() || $parameter->getDefaultValue() !== $arguments[$index]) {
break;
}
unset($arguments[$index]);
}
}
// it's possible index 1 was set, then index 0, then 2, etc
// make sure that we re-order so they're injected as expected
ksort($arguments);
return $arguments;
}
/**
* Returns a reference to the service matching the given type, if any.
*/
private function getAutowiredReference(TypedReference $reference): ?TypedReference
{
$this->lastFailure = null;
$type = $reference->getType();
if ($type !== (string) $reference) {
return $reference;
}
if (null !== $name = $reference->getName()) {
if ($this->container->has($alias = $type.' $'.$name) && !$this->container->findDefinition($alias)->isAbstract()) {
return new TypedReference($alias, $type, $reference->getInvalidBehavior());
}
if ($this->container->has($name) && !$this->container->findDefinition($name)->isAbstract()) {
foreach ($this->container->getAliases() as $id => $alias) {
if ($name === (string) $alias && 0 === strpos($id, $type.' $')) {
return new TypedReference($name, $type, $reference->getInvalidBehavior());
}
}
}
}
if ($this->container->has($type) && !$this->container->findDefinition($type)->isAbstract()) {
return new TypedReference($type, $type, $reference->getInvalidBehavior());
}
return null;
}
/**
* Populates the list of available types.
*/
private function populateAvailableTypes(ContainerBuilder $container)
{
$this->types = [];
$this->ambiguousServiceTypes = [];
foreach ($container->getDefinitions() as $id => $definition) {
$this->populateAvailableType($container, $id, $definition);
}
}
/**
* Populates the list of available types for a given definition.
*/
private function populateAvailableType(ContainerBuilder $container, string $id, Definition $definition)
{
// Never use abstract services
if ($definition->isAbstract()) {
return;
}
if ('' === $id || '.' === $id[0] || $definition->isDeprecated() || !$reflectionClass = $container->getReflectionClass($definition->getClass(), false)) {
return;
}
foreach ($reflectionClass->getInterfaces() as $reflectionInterface) {
$this->set($reflectionInterface->name, $id);
}
do {
$this->set($reflectionClass->name, $id);
} while ($reflectionClass = $reflectionClass->getParentClass());
}
/**
* Associates a type and a service id if applicable.
*/
private function set(string $type, string $id)
{
// is this already a type/class that is known to match multiple services?
if (isset($this->ambiguousServiceTypes[$type])) {
$this->ambiguousServiceTypes[$type][] = $id;
return;
}
// check to make sure the type doesn't match multiple services
if (!isset($this->types[$type]) || $this->types[$type] === $id) {
$this->types[$type] = $id;
return;
}
// keep an array of all services matching this type
if (!isset($this->ambiguousServiceTypes[$type])) {
$this->ambiguousServiceTypes[$type] = [$this->types[$type]];
unset($this->types[$type]);
}
$this->ambiguousServiceTypes[$type][] = $id;
}
private function createTypeNotFoundMessageCallback(TypedReference $reference, string $label): callable
{
if (null === $this->typesClone->container) {
$this->typesClone->container = new ContainerBuilder($this->container->getParameterBag());
$this->typesClone->container->setAliases($this->container->getAliases());
$this->typesClone->container->setDefinitions($this->container->getDefinitions());
$this->typesClone->container->setResourceTracking(false);
}
$currentId = $this->currentId;
return (function () use ($reference, $label, $currentId) {
return $this->createTypeNotFoundMessage($reference, $label, $currentId);
})->bindTo($this->typesClone);
}
private function createTypeNotFoundMessage(TypedReference $reference, string $label, string $currentId): string
{
if (!$r = $this->container->getReflectionClass($type = $reference->getType(), false)) {
// either $type does not exist or a parent class does not exist
try {
$resource = new ClassExistenceResource($type, false);
// isFresh() will explode ONLY if a parent class/trait does not exist
$resource->isFresh(0);
$parentMsg = false;
} catch (\ReflectionException $e) {
$parentMsg = $e->getMessage();
}
$message = sprintf('has type "%s" but this class %s.', $type, $parentMsg ? sprintf('is missing a parent class (%s)', $parentMsg) : 'was not found');
} else {
$alternatives = $this->createTypeAlternatives($this->container, $reference);
$message = $this->container->has($type) ? 'this service is abstract' : 'no such service exists';
$message = sprintf('references %s "%s" but %s.%s', $r->isInterface() ? 'interface' : 'class', $type, $message, $alternatives);
if ($r->isInterface() && !$alternatives) {
$message .= ' Did you create a class that implements this interface?';
}
}
$message = sprintf('Cannot autowire service "%s": %s %s', $currentId, $label, $message);
if (null !== $this->lastFailure) {
$message = $this->lastFailure."\n".$message;
$this->lastFailure = null;
}
return $message;
}
private function createTypeAlternatives(ContainerBuilder $container, TypedReference $reference): string
{
// try suggesting available aliases first
if ($message = $this->getAliasesSuggestionForType($container, $type = $reference->getType())) {
return ' '.$message;
}
if (null === $this->ambiguousServiceTypes) {
$this->populateAvailableTypes($container);
}
$servicesAndAliases = $container->getServiceIds();
if (!$container->has($type) && false !== $key = array_search(strtolower($type), array_map('strtolower', $servicesAndAliases))) {
return sprintf(' Did you mean "%s"?', $servicesAndAliases[$key]);
} elseif (isset($this->ambiguousServiceTypes[$type])) {
$message = sprintf('one of these existing services: "%s"', implode('", "', $this->ambiguousServiceTypes[$type]));
} elseif (isset($this->types[$type])) {
$message = sprintf('the existing "%s" service', $this->types[$type]);
} else {
return '';
}
return sprintf(' You should maybe alias this %s to %s.', class_exists($type, false) ? 'class' : 'interface', $message);
}
private function getAliasesSuggestionForType(ContainerBuilder $container, string $type): ?string
{
$aliases = [];
foreach (class_parents($type) + class_implements($type) as $parent) {
if ($container->has($parent) && !$container->findDefinition($parent)->isAbstract()) {
$aliases[] = $parent;
}
}
if (1 < $len = \count($aliases)) {
$message = 'Try changing the type-hint to one of its parents: ';
for ($i = 0, --$len; $i < $len; ++$i) {
$message .= sprintf('%s "%s", ', class_exists($aliases[$i], false) ? 'class' : 'interface', $aliases[$i]);
}
$message .= sprintf('or %s "%s".', class_exists($aliases[$i], false) ? 'class' : 'interface', $aliases[$i]);
return $message;
}
if ($aliases) {
return sprintf('Try changing the type-hint to "%s" instead.', $aliases[0]);
}
return null;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
/**
* Checks if arguments of methods are properly configured.
*
* @author Kévin Dunglas <dunglas@gmail.com>
* @author Nicolas Grekas <p@tchwork.com>
*/
class CheckArgumentsValidityPass extends AbstractRecursivePass
{
private $throwExceptions;
public function __construct(bool $throwExceptions = true)
{
$this->throwExceptions = $throwExceptions;
}
/**
* {@inheritdoc}
*/
protected function processValue($value, bool $isRoot = false)
{
if (!$value instanceof Definition) {
return parent::processValue($value, $isRoot);
}
$i = 0;
foreach ($value->getArguments() as $k => $v) {
if ($k !== $i++) {
if (!\is_int($k)) {
$msg = sprintf('Invalid constructor argument for service "%s": integer expected but found string "%s". Check your service definition.', $this->currentId, $k);
$value->addError($msg);
if ($this->throwExceptions) {
throw new RuntimeException($msg);
}
break;
}
$msg = sprintf('Invalid constructor argument %d for service "%s": argument %d must be defined before. Check your service definition.', 1 + $k, $this->currentId, $i);
$value->addError($msg);
if ($this->throwExceptions) {
throw new RuntimeException($msg);
}
}
}
foreach ($value->getMethodCalls() as $methodCall) {
$i = 0;
foreach ($methodCall[1] as $k => $v) {
if ($k !== $i++) {
if (!\is_int($k)) {
$msg = sprintf('Invalid argument for method call "%s" of service "%s": integer expected but found string "%s". Check your service definition.', $methodCall[0], $this->currentId, $k);
$value->addError($msg);
if ($this->throwExceptions) {
throw new RuntimeException($msg);
}
break;
}
$msg = sprintf('Invalid argument %d for method call "%s" of service "%s": argument %d must be defined before. Check your service definition.', 1 + $k, $methodCall[0], $this->currentId, $i);
$value->addError($msg);
if ($this->throwExceptions) {
throw new RuntimeException($msg);
}
}
}
}
return null;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
/**
* This is a directed graph of your services.
*
* This information can be used by your compiler passes instead of collecting
* it themselves which improves performance quite a lot.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*
* @final
*/
class ServiceReferenceGraph
{
/**
* @var ServiceReferenceGraphNode[]
*/
private $nodes = [];
public function hasNode(string $id): bool
{
return isset($this->nodes[$id]);
}
/**
* Gets a node by identifier.
*
* @throws InvalidArgumentException if no node matches the supplied identifier
*/
public function getNode(string $id): ServiceReferenceGraphNode
{
if (!isset($this->nodes[$id])) {
throw new InvalidArgumentException(sprintf('There is no node with id "%s".', $id));
}
return $this->nodes[$id];
}
/**
* Returns all nodes.
*
* @return ServiceReferenceGraphNode[]
*/
public function getNodes(): array
{
return $this->nodes;
}
/**
* Clears all nodes.
*/
public function clear()
{
foreach ($this->nodes as $node) {
$node->clear();
}
$this->nodes = [];
}
/**
* Connects 2 nodes together in the Graph.
*/
public function connect(?string $sourceId, $sourceValue, ?string $destId, $destValue = null, $reference = null, bool $lazy = false, bool $weak = false, bool $byConstructor = false)
{
if (null === $sourceId || null === $destId) {
return;
}
$sourceNode = $this->createNode($sourceId, $sourceValue);
$destNode = $this->createNode($destId, $destValue);
$edge = new ServiceReferenceGraphEdge($sourceNode, $destNode, $reference, $lazy, $weak, $byConstructor);
$sourceNode->addOutEdge($edge);
$destNode->addInEdge($edge);
}
private function createNode(string $id, $value): ServiceReferenceGraphNode
{
if (isset($this->nodes[$id]) && $this->nodes[$id]->getValue() === $value) {
return $this->nodes[$id];
}
return $this->nodes[$id] = new ServiceReferenceGraphNode($id, $value);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Argument\BoundArgument;
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
use Symfony\Component\DependencyInjection\LazyProxy\ProxyHelper;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\TypedReference;
/**
* @author Guilhem Niot <guilhem.niot@gmail.com>
*/
class ResolveBindingsPass extends AbstractRecursivePass
{
private $usedBindings = [];
private $unusedBindings = [];
private $errorMessages = [];
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container)
{
$this->usedBindings = $container->getRemovedBindingIds();
try {
parent::process($container);
foreach ($this->unusedBindings as [$key, $serviceId, $bindingType, $file]) {
$argumentType = $argumentName = $message = null;
if (false !== strpos($key, ' ')) {
[$argumentType, $argumentName] = explode(' ', $key, 2);
} elseif ('$' === $key[0]) {
$argumentName = $key;
} else {
$argumentType = $key;
}
if ($argumentType) {
$message .= sprintf('of type "%s" ', $argumentType);
}
if ($argumentName) {
$message .= sprintf('named "%s" ', $argumentName);
}
if (BoundArgument::DEFAULTS_BINDING === $bindingType) {
$message .= 'under "_defaults"';
} elseif (BoundArgument::INSTANCEOF_BINDING === $bindingType) {
$message .= 'under "_instanceof"';
} else {
$message .= sprintf('for service "%s"', $serviceId);
}
if ($file) {
$message .= sprintf(' in file "%s"', $file);
}
$message = sprintf('A binding is configured for an argument %s, but no corresponding argument has been found. It may be unused and should be removed, or it may have a typo.', $message);
if ($this->errorMessages) {
$message .= sprintf("\nCould be related to%s:", 1 < \count($this->errorMessages) ? ' one of' : '');
}
foreach ($this->errorMessages as $m) {
$message .= "\n - ".$m;
}
throw new InvalidArgumentException($message);
}
} finally {
$this->usedBindings = [];
$this->unusedBindings = [];
$this->errorMessages = [];
}
}
/**
* {@inheritdoc}
*/
protected function processValue($value, bool $isRoot = false)
{
if ($value instanceof TypedReference && $value->getType() === (string) $value) {
// Already checked
$bindings = $this->container->getDefinition($this->currentId)->getBindings();
$name = $value->getName();
if (isset($name, $bindings[$name = $value.' $'.$name])) {
return $this->getBindingValue($bindings[$name]);
}
if (isset($bindings[$value->getType()])) {
return $this->getBindingValue($bindings[$value->getType()]);
}
return parent::processValue($value, $isRoot);
}
if (!$value instanceof Definition || !$bindings = $value->getBindings()) {
return parent::processValue($value, $isRoot);
}
$bindingNames = [];
foreach ($bindings as $key => $binding) {
[$bindingValue, $bindingId, $used, $bindingType, $file] = $binding->getValues();
if ($used) {
$this->usedBindings[$bindingId] = true;
unset($this->unusedBindings[$bindingId]);
} elseif (!isset($this->usedBindings[$bindingId])) {
$this->unusedBindings[$bindingId] = [$key, $this->currentId, $bindingType, $file];
}
if (preg_match('/^(?:(?:array|bool|float|int|string|([^ $]++)) )\$/', $key, $m)) {
$bindingNames[substr($key, \strlen($m[0]))] = $binding;
}
if (!isset($m[1])) {
continue;
}
if (null !== $bindingValue && !$bindingValue instanceof Reference && !$bindingValue instanceof Definition && !$bindingValue instanceof TaggedIteratorArgument && !$bindingValue instanceof ServiceLocatorArgument) {
throw new InvalidArgumentException(sprintf('Invalid value for binding key "%s" for service "%s": expected null, "%s", "%s", "%s" or ServiceLocatorArgument, "%s" given.', $key, $this->currentId, Reference::class, Definition::class, TaggedIteratorArgument::class, get_debug_type($bindingValue)));
}
}
if ($value->isAbstract()) {
return parent::processValue($value, $isRoot);
}
$calls = $value->getMethodCalls();
try {
if ($constructor = $this->getConstructor($value, false)) {
$calls[] = [$constructor, $value->getArguments()];
}
} catch (RuntimeException $e) {
$this->errorMessages[] = $e->getMessage();
$this->container->getDefinition($this->currentId)->addError($e->getMessage());
return parent::processValue($value, $isRoot);
}
foreach ($calls as $i => $call) {
[$method, $arguments] = $call;
if ($method instanceof \ReflectionFunctionAbstract) {
$reflectionMethod = $method;
} else {
try {
$reflectionMethod = $this->getReflectionMethod($value, $method);
} catch (RuntimeException $e) {
if ($value->getFactory()) {
continue;
}
throw $e;
}
}
foreach ($reflectionMethod->getParameters() as $key => $parameter) {
if (\array_key_exists($key, $arguments) && '' !== $arguments[$key]) {
continue;
}
$typeHint = ProxyHelper::getTypeHint($reflectionMethod, $parameter);
if (\array_key_exists($k = ltrim($typeHint, '\\').' $'.$parameter->name, $bindings)) {
$arguments[$key] = $this->getBindingValue($bindings[$k]);
continue;
}
if (\array_key_exists('$'.$parameter->name, $bindings)) {
$arguments[$key] = $this->getBindingValue($bindings['$'.$parameter->name]);
continue;
}
if ($typeHint && '\\' === $typeHint[0] && isset($bindings[$typeHint = substr($typeHint, 1)])) {
$arguments[$key] = $this->getBindingValue($bindings[$typeHint]);
continue;
}
if (isset($bindingNames[$parameter->name])) {
$bindingKey = array_search($binding, $bindings, true);
$argumentType = substr($bindingKey, 0, strpos($bindingKey, ' '));
$this->errorMessages[] = sprintf('Did you forget to add the type "%s" to argument "$%s" of method "%s::%s()"?', $argumentType, $parameter->name, $reflectionMethod->class, $reflectionMethod->name);
}
}
if ($arguments !== $call[1]) {
ksort($arguments);
$calls[$i][1] = $arguments;
}
}
if ($constructor) {
[, $arguments] = array_pop($calls);
if ($arguments !== $value->getArguments()) {
$value->setArguments($arguments);
}
}
if ($calls !== $value->getMethodCalls()) {
$value->setMethodCalls($calls);
}
return parent::processValue($value, $isRoot);
}
/**
* @return mixed
*/
private function getBindingValue(BoundArgument $binding)
{
[$bindingValue, $bindingId] = $binding->getValues();
$this->usedBindings[$bindingId] = true;
unset($this->unusedBindings[$bindingId]);
return $bindingValue;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
/**
* @author Maxime Steinhausser <maxime.steinhausser@gmail.com>
*/
class ResolveFactoryClassPass extends AbstractRecursivePass
{
/**
* {@inheritdoc}
*/
protected function processValue($value, bool $isRoot = false)
{
if ($value instanceof Definition && \is_array($factory = $value->getFactory()) && null === $factory[0]) {
if (null === $class = $value->getClass()) {
throw new RuntimeException(sprintf('The "%s" service is defined to be created by a factory, but is missing the factory class. Did you forget to define the factory or service class?', $this->currentId));
}
$factory[0] = $class;
$value->setFactory($factory);
}
return parent::processValue($value, $isRoot);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
/**
* Compiler Pass Configuration.
*
* This class has a default configuration embedded.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class PassConfig
{
const TYPE_AFTER_REMOVING = 'afterRemoving';
const TYPE_BEFORE_OPTIMIZATION = 'beforeOptimization';
const TYPE_BEFORE_REMOVING = 'beforeRemoving';
const TYPE_OPTIMIZE = 'optimization';
const TYPE_REMOVE = 'removing';
private $mergePass;
private $afterRemovingPasses = [];
private $beforeOptimizationPasses = [];
private $beforeRemovingPasses = [];
private $optimizationPasses;
private $removingPasses;
public function __construct()
{
$this->mergePass = new MergeExtensionConfigurationPass();
$this->beforeOptimizationPasses = [
100 => [
new ResolveClassPass(),
new ResolveInstanceofConditionalsPass(),
new RegisterEnvVarProcessorsPass(),
],
-1000 => [new ExtensionCompilerPass()],
];
$this->optimizationPasses = [[
new AutoAliasServicePass(),
new ValidateEnvPlaceholdersPass(),
new ResolveDecoratorStackPass(),
new ResolveChildDefinitionsPass(),
new RegisterServiceSubscribersPass(),
new ResolveParameterPlaceHoldersPass(false, false),
new ResolveFactoryClassPass(),
new ResolveNamedArgumentsPass(),
new AutowireRequiredMethodsPass(),
new AutowireRequiredPropertiesPass(),
new ResolveBindingsPass(),
new ServiceLocatorTagPass(),
new DecoratorServicePass(),
new CheckDefinitionValidityPass(),
new AutowirePass(false),
new ResolveTaggedIteratorArgumentPass(),
new ResolveServiceSubscribersPass(),
new ResolveReferencesToAliasesPass(),
new ResolveInvalidReferencesPass(),
new AnalyzeServiceReferencesPass(true),
new CheckCircularReferencesPass(),
new CheckReferenceValidityPass(),
new CheckArgumentsValidityPass(false),
]];
$this->removingPasses = [[
new RemovePrivateAliasesPass(),
new ReplaceAliasByActualDefinitionPass(),
new RemoveAbstractDefinitionsPass(),
new RemoveUnusedDefinitionsPass(),
new InlineServiceDefinitionsPass(new AnalyzeServiceReferencesPass()),
new AnalyzeServiceReferencesPass(),
new DefinitionErrorExceptionPass(),
]];
$this->afterRemovingPasses = [[
new CheckExceptionOnInvalidReferenceBehaviorPass(),
new ResolveHotPathPass(),
new ResolveNoPreloadPass(),
new AliasDeprecatedPublicServicesPass(),
]];
}
/**
* Returns all passes in order to be processed.
*
* @return CompilerPassInterface[]
*/
public function getPasses()
{
return array_merge(
[$this->mergePass],
$this->getBeforeOptimizationPasses(),
$this->getOptimizationPasses(),
$this->getBeforeRemovingPasses(),
$this->getRemovingPasses(),
$this->getAfterRemovingPasses()
);
}
/**
* Adds a pass.
*
* @throws InvalidArgumentException when a pass type doesn't exist
*/
public function addPass(CompilerPassInterface $pass, string $type = self::TYPE_BEFORE_OPTIMIZATION, int $priority = 0)
{
$property = $type.'Passes';
if (!isset($this->$property)) {
throw new InvalidArgumentException(sprintf('Invalid type "%s".', $type));
}
$passes = &$this->$property;
if (!isset($passes[$priority])) {
$passes[$priority] = [];
}
$passes[$priority][] = $pass;
}
/**
* Gets all passes for the AfterRemoving pass.
*
* @return CompilerPassInterface[]
*/
public function getAfterRemovingPasses()
{
return $this->sortPasses($this->afterRemovingPasses);
}
/**
* Gets all passes for the BeforeOptimization pass.
*
* @return CompilerPassInterface[]
*/
public function getBeforeOptimizationPasses()
{
return $this->sortPasses($this->beforeOptimizationPasses);
}
/**
* Gets all passes for the BeforeRemoving pass.
*
* @return CompilerPassInterface[]
*/
public function getBeforeRemovingPasses()
{
return $this->sortPasses($this->beforeRemovingPasses);
}
/**
* Gets all passes for the Optimization pass.
*
* @return CompilerPassInterface[]
*/
public function getOptimizationPasses()
{
return $this->sortPasses($this->optimizationPasses);
}
/**
* Gets all passes for the Removing pass.
*
* @return CompilerPassInterface[]
*/
public function getRemovingPasses()
{
return $this->sortPasses($this->removingPasses);
}
/**
* Gets the Merge pass.
*
* @return CompilerPassInterface
*/
public function getMergePass()
{
return $this->mergePass;
}
public function setMergePass(CompilerPassInterface $pass)
{
$this->mergePass = $pass;
}
/**
* Sets the AfterRemoving passes.
*
* @param CompilerPassInterface[] $passes
*/
public function setAfterRemovingPasses(array $passes)
{
$this->afterRemovingPasses = [$passes];
}
/**
* Sets the BeforeOptimization passes.
*
* @param CompilerPassInterface[] $passes
*/
public function setBeforeOptimizationPasses(array $passes)
{
$this->beforeOptimizationPasses = [$passes];
}
/**
* Sets the BeforeRemoving passes.
*
* @param CompilerPassInterface[] $passes
*/
public function setBeforeRemovingPasses(array $passes)
{
$this->beforeRemovingPasses = [$passes];
}
/**
* Sets the Optimization passes.
*
* @param CompilerPassInterface[] $passes
*/
public function setOptimizationPasses(array $passes)
{
$this->optimizationPasses = [$passes];
}
/**
* Sets the Removing passes.
*
* @param CompilerPassInterface[] $passes
*/
public function setRemovingPasses(array $passes)
{
$this->removingPasses = [$passes];
}
/**
* Sort passes by priority.
*
* @param array $passes CompilerPassInterface instances with their priority as key
*
* @return CompilerPassInterface[]
*/
private function sortPasses(array $passes): array
{
if (0 === \count($passes)) {
return [];
}
krsort($passes);
// Flatten the array
return array_merge(...$passes);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Psr\Container\ContainerInterface;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Contracts\Service\ServiceProviderInterface;
/**
* Compiler pass to inject their service locator to service subscribers.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
class ResolveServiceSubscribersPass extends AbstractRecursivePass
{
private $serviceLocator;
protected function processValue($value, bool $isRoot = false)
{
if ($value instanceof Reference && $this->serviceLocator && \in_array((string) $value, [ContainerInterface::class, ServiceProviderInterface::class], true)) {
return new Reference($this->serviceLocator);
}
if (!$value instanceof Definition) {
return parent::processValue($value, $isRoot);
}
$serviceLocator = $this->serviceLocator;
$this->serviceLocator = null;
if ($value->hasTag('container.service_subscriber.locator')) {
$this->serviceLocator = $value->getTag('container.service_subscriber.locator')[0]['id'];
$value->clearTag('container.service_subscriber.locator');
}
try {
return parent::processValue($value);
} finally {
$this->serviceLocator = $serviceLocator;
}
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Alias;
use Symfony\Component\DependencyInjection\Definition;
/**
* Represents a node in your service graph.
*
* Value is typically a definition, or an alias.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class ServiceReferenceGraphNode
{
private $id;
private $inEdges = [];
private $outEdges = [];
private $value;
/**
* @param string $id The node identifier
* @param mixed $value The node value
*/
public function __construct(string $id, $value)
{
$this->id = $id;
$this->value = $value;
}
public function addInEdge(ServiceReferenceGraphEdge $edge)
{
$this->inEdges[] = $edge;
}
public function addOutEdge(ServiceReferenceGraphEdge $edge)
{
$this->outEdges[] = $edge;
}
/**
* Checks if the value of this node is an Alias.
*
* @return bool True if the value is an Alias instance
*/
public function isAlias()
{
return $this->value instanceof Alias;
}
/**
* Checks if the value of this node is a Definition.
*
* @return bool True if the value is a Definition instance
*/
public function isDefinition()
{
return $this->value instanceof Definition;
}
/**
* Returns the identifier.
*
* @return string
*/
public function getId()
{
return $this->id;
}
/**
* Returns the in edges.
*
* @return ServiceReferenceGraphEdge[]
*/
public function getInEdges()
{
return $this->inEdges;
}
/**
* Returns the out edges.
*
* @return ServiceReferenceGraphEdge[]
*/
public function getOutEdges()
{
return $this->outEdges;
}
/**
* Returns the value of this Node.
*
* @return mixed The value
*/
public function getValue()
{
return $this->value;
}
/**
* Clears all edges.
*/
public function clear()
{
$this->inEdges = $this->outEdges = [];
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\ContainerBuilder;
/**
* A pass to automatically process extensions if they implement
* CompilerPassInterface.
*
* @author Wouter J <wouter@wouterj.nl>
*/
class ExtensionCompilerPass implements CompilerPassInterface
{
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container)
{
foreach ($container->getExtensions() as $extension) {
if (!$extension instanceof CompilerPassInterface) {
continue;
}
$extension->process($container);
}
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
trigger_deprecation('symfony/dependency-injection', '5.2', 'The "%s" class is deprecated.', ResolvePrivatesPass::class);
use Symfony\Component\DependencyInjection\ContainerBuilder;
/**
* @author Nicolas Grekas <p@tchwork.com>
*
* @deprecated since Symfony 5.2
*/
class ResolvePrivatesPass implements CompilerPassInterface
{
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container)
{
foreach ($container->getDefinitions() as $id => $definition) {
if ($definition->isPrivate()) {
$definition->setPublic(false);
$definition->setPrivate(true);
}
}
foreach ($container->getAliases() as $id => $alias) {
if ($alias->isPrivate()) {
$alias->setPublic(false);
$alias->setPrivate(true);
}
}
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
use Symfony\Component\DependencyInjection\Reference;
/**
* Replaces aliases with actual service definitions, effectively removing these
* aliases.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class ReplaceAliasByActualDefinitionPass extends AbstractRecursivePass
{
private $replacements;
/**
* Process the Container to replace aliases with service definitions.
*
* @throws InvalidArgumentException if the service definition does not exist
*/
public function process(ContainerBuilder $container)
{
// First collect all alias targets that need to be replaced
$seenAliasTargets = [];
$replacements = [];
foreach ($container->getAliases() as $definitionId => $target) {
$targetId = (string) $target;
// Special case: leave this target alone
if ('service_container' === $targetId) {
continue;
}
// Check if target needs to be replaces
if (isset($replacements[$targetId])) {
$container->setAlias($definitionId, $replacements[$targetId])->setPublic($target->isPublic());
}
// No need to process the same target twice
if (isset($seenAliasTargets[$targetId])) {
continue;
}
// Process new target
$seenAliasTargets[$targetId] = true;
try {
$definition = $container->getDefinition($targetId);
} catch (ServiceNotFoundException $e) {
if ('' !== $e->getId() && '@' === $e->getId()[0]) {
throw new ServiceNotFoundException($e->getId(), $e->getSourceId(), null, [substr($e->getId(), 1)]);
}
throw $e;
}
if ($definition->isPublic()) {
continue;
}
// Remove private definition and schedule for replacement
$definition->setPublic($target->isPublic());
$container->setDefinition($definitionId, $definition);
$container->removeDefinition($targetId);
$replacements[$targetId] = $definitionId;
}
$this->replacements = $replacements;
parent::process($container);
$this->replacements = [];
}
/**
* {@inheritdoc}
*/
protected function processValue($value, bool $isRoot = false)
{
if ($value instanceof Reference && isset($this->replacements[$referenceId = (string) $value])) {
// Perform the replacement
$newId = $this->replacements[$referenceId];
$value = new Reference($newId, $value->getInvalidBehavior());
$this->container->log($this, sprintf('Changed reference of service "%s" previously pointing to "%s" to "%s".', $this->currentId, $referenceId, $newId));
}
return parent::processValue($value, $isRoot);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
/**
* Removes unused service definitions from the container.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
* @author Nicolas Grekas <p@tchwork.com>
*/
class RemoveUnusedDefinitionsPass extends AbstractRecursivePass
{
private $connectedIds = [];
/**
* Processes the ContainerBuilder to remove unused definitions.
*/
public function process(ContainerBuilder $container)
{
try {
$this->enableExpressionProcessing();
$this->container = $container;
$connectedIds = [];
$aliases = $container->getAliases();
foreach ($aliases as $id => $alias) {
if ($alias->isPublic()) {
$this->connectedIds[] = (string) $aliases[$id];
}
}
foreach ($container->getDefinitions() as $id => $definition) {
if ($definition->isPublic()) {
$connectedIds[$id] = true;
$this->processValue($definition);
}
}
while ($this->connectedIds) {
$ids = $this->connectedIds;
$this->connectedIds = [];
foreach ($ids as $id) {
if (!isset($connectedIds[$id]) && $container->hasDefinition($id)) {
$connectedIds[$id] = true;
$this->processValue($container->getDefinition($id));
}
}
}
foreach ($container->getDefinitions() as $id => $definition) {
if (!isset($connectedIds[$id])) {
$container->removeDefinition($id);
$container->resolveEnvPlaceholders(!$definition->hasErrors() ? serialize($definition) : $definition);
$container->log($this, sprintf('Removed service "%s"; reason: unused.', $id));
}
}
} finally {
$this->container = null;
$this->connectedIds = [];
}
}
/**
* {@inheritdoc}
*/
protected function processValue($value, bool $isRoot = false)
{
if (!$value instanceof Reference) {
return parent::processValue($value, $isRoot);
}
if (ContainerBuilder::IGNORE_ON_UNINITIALIZED_REFERENCE !== $value->getInvalidBehavior()) {
$this->connectedIds[] = (string) $value;
}
return $value;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Argument\ArgumentInterface;
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\TypedReference;
/**
* Emulates the invalid behavior if the reference is not found within the
* container.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class ResolveInvalidReferencesPass implements CompilerPassInterface
{
private $container;
private $signalingException;
private $currentId;
/**
* Process the ContainerBuilder to resolve invalid references.
*/
public function process(ContainerBuilder $container)
{
$this->container = $container;
$this->signalingException = new RuntimeException('Invalid reference.');
try {
foreach ($container->getDefinitions() as $this->currentId => $definition) {
$this->processValue($definition);
}
} finally {
$this->container = $this->signalingException = null;
}
}
/**
* Processes arguments to determine invalid references.
*
* @return mixed
*
* @throws RuntimeException When an invalid reference is found
*/
private function processValue($value, int $rootLevel = 0, int $level = 0)
{
if ($value instanceof ServiceClosureArgument) {
$value->setValues($this->processValue($value->getValues(), 1, 1));
} elseif ($value instanceof ArgumentInterface) {
$value->setValues($this->processValue($value->getValues(), $rootLevel, 1 + $level));
} elseif ($value instanceof Definition) {
if ($value->isSynthetic() || $value->isAbstract()) {
return $value;
}
$value->setArguments($this->processValue($value->getArguments(), 0));
$value->setProperties($this->processValue($value->getProperties(), 1));
$value->setMethodCalls($this->processValue($value->getMethodCalls(), 2));
} elseif (\is_array($value)) {
$i = 0;
foreach ($value as $k => $v) {
try {
if (false !== $i && $k !== $i++) {
$i = false;
}
if ($v !== $processedValue = $this->processValue($v, $rootLevel, 1 + $level)) {
$value[$k] = $processedValue;
}
} catch (RuntimeException $e) {
if ($rootLevel < $level || ($rootLevel && !$level)) {
unset($value[$k]);
} elseif ($rootLevel) {
throw $e;
} else {
$value[$k] = null;
}
}
}
// Ensure numerically indexed arguments have sequential numeric keys.
if (false !== $i) {
$value = array_values($value);
}
} elseif ($value instanceof Reference) {
if ($this->container->has($id = (string) $value)) {
return $value;
}
$currentDefinition = $this->container->getDefinition($this->currentId);
// resolve decorated service behavior depending on decorator service
if ($currentDefinition->innerServiceId === $id && ContainerInterface::NULL_ON_INVALID_REFERENCE === $currentDefinition->decorationOnInvalid) {
return null;
}
$invalidBehavior = $value->getInvalidBehavior();
if (ContainerInterface::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE === $invalidBehavior && $value instanceof TypedReference && !$this->container->has($id)) {
$e = new ServiceNotFoundException($id, $this->currentId);
// since the error message varies by $id and $this->currentId, so should the id of the dummy errored definition
$this->container->register($id = sprintf('.errored.%s.%s', $this->currentId, $id), $value->getType())
->addError($e->getMessage());
return new TypedReference($id, $value->getType(), $value->getInvalidBehavior());
}
// resolve invalid behavior
if (ContainerInterface::NULL_ON_INVALID_REFERENCE === $invalidBehavior) {
$value = null;
} elseif (ContainerInterface::IGNORE_ON_INVALID_REFERENCE === $invalidBehavior) {
if (0 < $level || $rootLevel) {
throw $this->signalingException;
}
$value = null;
}
}
return $value;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\ContainerBuilder;
/**
* Removes abstract Definitions.
*/
class RemoveAbstractDefinitionsPass implements CompilerPassInterface
{
/**
* Removes abstract definitions from the ContainerBuilder.
*/
public function process(ContainerBuilder $container)
{
foreach ($container->getDefinitions() as $id => $definition) {
if ($definition->isAbstract()) {
$container->removeDefinition($id);
$container->log($this, sprintf('Removed service "%s"; reason: abstract.', $id));
}
}
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Alias;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException;
use Symfony\Component\DependencyInjection\Reference;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
class ResolveDecoratorStackPass implements CompilerPassInterface
{
private $tag;
public function __construct(string $tag = 'container.stack')
{
$this->tag = $tag;
}
public function process(ContainerBuilder $container)
{
$stacks = [];
foreach ($container->findTaggedServiceIds($this->tag) as $id => $tags) {
$definition = $container->getDefinition($id);
if (!$definition instanceof ChildDefinition) {
throw new InvalidArgumentException(sprintf('Invalid service "%s": only definitions with a "parent" can have the "%s" tag.', $id, $this->tag));
}
if (!$stack = $definition->getArguments()) {
throw new InvalidArgumentException(sprintf('Invalid service "%s": the stack of decorators is empty.', $id));
}
$stacks[$id] = $stack;
}
if (!$stacks) {
return;
}
$resolvedDefinitions = [];
foreach ($container->getDefinitions() as $id => $definition) {
if (!isset($stacks[$id])) {
$resolvedDefinitions[$id] = $definition;
continue;
}
foreach (array_reverse($this->resolveStack($stacks, [$id]), true) as $k => $v) {
$resolvedDefinitions[$k] = $v;
}
$alias = $container->setAlias($id, $k);
if ($definition->getChanges()['public'] ?? false) {
$alias->setPublic($definition->isPublic());
}
if ($definition->isDeprecated()) {
$alias->setDeprecated(...array_values($definition->getDeprecation('%alias_id%')));
}
}
$container->setDefinitions($resolvedDefinitions);
}
private function resolveStack(array $stacks, array $path): array
{
$definitions = [];
$id = end($path);
$prefix = '.'.$id.'.';
if (!isset($stacks[$id])) {
return [$id => new ChildDefinition($id)];
}
if (key($path) !== $searchKey = array_search($id, $path)) {
throw new ServiceCircularReferenceException($id, \array_slice($path, $searchKey));
}
foreach ($stacks[$id] as $k => $definition) {
if ($definition instanceof ChildDefinition && isset($stacks[$definition->getParent()])) {
$path[] = $definition->getParent();
$definition = unserialize(serialize($definition)); // deep clone
} elseif ($definition instanceof Definition) {
$definitions[$decoratedId = $prefix.$k] = $definition;
continue;
} elseif ($definition instanceof Reference || $definition instanceof Alias) {
$path[] = (string) $definition;
} else {
throw new InvalidArgumentException(sprintf('Invalid service "%s": unexpected value of type "%s" found in the stack of decorators.', $id, get_debug_type($definition)));
}
$p = $prefix.$k;
foreach ($this->resolveStack($stacks, $path) as $k => $v) {
$definitions[$decoratedId = $p.$k] = $definition instanceof ChildDefinition ? $definition->setParent($k) : new ChildDefinition($k);
$definition = null;
}
array_pop($path);
}
if (1 === \count($path)) {
foreach ($definitions as $k => $definition) {
$definition->setPublic(false)->setTags([])->setDecoratedService($decoratedId);
}
$definition->setDecoratedService(null);
}
return $definitions;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\TypedReference;
/**
* Trait that allows a generic method to find and sort service by priority option in the tag.
*
* @author Iltar van der Berg <kjarli@gmail.com>
*/
trait PriorityTaggedServiceTrait
{
/**
* Finds all services with the given tag name and order them by their priority.
*
* The order of additions must be respected for services having the same priority,
* and knowing that the \SplPriorityQueue class does not respect the FIFO method,
* we should not use that class.
*
* @see https://bugs.php.net/53710
* @see https://bugs.php.net/60926
*
* @param string|TaggedIteratorArgument $tagName
*
* @return Reference[]
*/
private function findAndSortTaggedServices($tagName, ContainerBuilder $container): array
{
$indexAttribute = $defaultIndexMethod = $needsIndexes = $defaultPriorityMethod = null;
if ($tagName instanceof TaggedIteratorArgument) {
$indexAttribute = $tagName->getIndexAttribute();
$defaultIndexMethod = $tagName->getDefaultIndexMethod();
$needsIndexes = $tagName->needsIndexes();
$defaultPriorityMethod = $tagName->getDefaultPriorityMethod();
$tagName = $tagName->getTag();
}
$i = 0;
$services = [];
foreach ($container->findTaggedServiceIds($tagName, true) as $serviceId => $attributes) {
$defaultPriority = null;
$defaultIndex = null;
$class = $container->getDefinition($serviceId)->getClass();
$class = $container->getParameterBag()->resolveValue($class) ?: null;
foreach ($attributes as $attribute) {
$index = $priority = null;
if (isset($attribute['priority'])) {
$priority = $attribute['priority'];
} elseif (null === $defaultPriority && $defaultPriorityMethod && $class) {
$defaultPriority = PriorityTaggedServiceUtil::getDefaultPriority($container, $serviceId, $class, $defaultPriorityMethod, $tagName);
}
$priority = $priority ?? $defaultPriority ?? $defaultPriority = 0;
if (null === $indexAttribute && !$needsIndexes) {
$services[] = [$priority, ++$i, null, $serviceId, null];
continue 2;
}
if (null !== $indexAttribute && isset($attribute[$indexAttribute])) {
$index = $attribute[$indexAttribute];
} elseif (null === $defaultIndex && $defaultIndexMethod && $class) {
$defaultIndex = PriorityTaggedServiceUtil::getDefaultIndex($container, $serviceId, $class, $defaultIndexMethod, $tagName, $indexAttribute);
}
$index = $index ?? $defaultIndex ?? $defaultIndex = $serviceId;
$services[] = [$priority, ++$i, $index, $serviceId, $class];
}
}
uasort($services, static function ($a, $b) { return $b[0] <=> $a[0] ?: $a[1] <=> $b[1]; });
$refs = [];
foreach ($services as [, , $index, $serviceId, $class]) {
if (!$class) {
$reference = new Reference($serviceId);
} elseif ($index === $serviceId) {
$reference = new TypedReference($serviceId, $class);
} else {
$reference = new TypedReference($serviceId, $class, ContainerBuilder::EXCEPTION_ON_INVALID_REFERENCE, $index);
}
if (null === $index) {
$refs[] = $reference;
} else {
$refs[$index] = $reference;
}
}
return $refs;
}
}
/**
* @internal
*/
class PriorityTaggedServiceUtil
{
/**
* Gets the index defined by the default index method.
*/
public static function getDefaultIndex(ContainerBuilder $container, string $serviceId, string $class, string $defaultIndexMethod, string $tagName, string $indexAttribute): ?string
{
if (!($r = $container->getReflectionClass($class)) || !$r->hasMethod($defaultIndexMethod)) {
return null;
}
if (!($rm = $r->getMethod($defaultIndexMethod))->isStatic()) {
throw new InvalidArgumentException(sprintf('Either method "%s::%s()" should be static or tag "%s" on service "%s" is missing attribute "%s".', $class, $defaultIndexMethod, $tagName, $serviceId, $indexAttribute));
}
if (!$rm->isPublic()) {
throw new InvalidArgumentException(sprintf('Either method "%s::%s()" should be public or tag "%s" on service "%s" is missing attribute "%s".', $class, $defaultIndexMethod, $tagName, $serviceId, $indexAttribute));
}
$defaultIndex = $rm->invoke(null);
if (!\is_string($defaultIndex)) {
throw new InvalidArgumentException(sprintf('Either method "%s::%s()" should return a string (got "%s") or tag "%s" on service "%s" is missing attribute "%s".', $class, $defaultIndexMethod, get_debug_type($defaultIndex), $tagName, $serviceId, $indexAttribute));
}
return $defaultIndex;
}
/**
* Gets the priority defined by the default priority method.
*/
public static function getDefaultPriority(ContainerBuilder $container, string $serviceId, string $class, string $defaultPriorityMethod, string $tagName): ?int
{
if (!($r = $container->getReflectionClass($class)) || !$r->hasMethod($defaultPriorityMethod)) {
return null;
}
if (!($rm = $r->getMethod($defaultPriorityMethod))->isStatic()) {
throw new InvalidArgumentException(sprintf('Either method "%s::%s()" should be static or tag "%s" on service "%s" is missing attribute "priority".', $class, $defaultPriorityMethod, $tagName, $serviceId));
}
if (!$rm->isPublic()) {
throw new InvalidArgumentException(sprintf('Either method "%s::%s()" should be public or tag "%s" on service "%s" is missing attribute "priority".', $class, $defaultPriorityMethod, $tagName, $serviceId));
}
$defaultPriority = $rm->invoke(null);
if (!\is_int($defaultPriority)) {
throw new InvalidArgumentException(sprintf('Method "%s::%s()" should return an integer (got "%s") or tag "%s" on service "%s" is missing attribute "priority".', $class, $defaultPriorityMethod, get_debug_type($defaultPriority), $tagName, $serviceId));
}
return $defaultPriority;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
/**
* Resolves all TaggedIteratorArgument arguments.
*
* @author Roland Franssen <franssen.roland@gmail.com>
*/
class ResolveTaggedIteratorArgumentPass extends AbstractRecursivePass
{
use PriorityTaggedServiceTrait;
/**
* {@inheritdoc}
*/
protected function processValue($value, bool $isRoot = false)
{
if (!$value instanceof TaggedIteratorArgument) {
return parent::processValue($value, $isRoot);
}
$value->setValues($this->findAndSortTaggedServices($value, $this->container));
return $value;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\ContainerBuilder;
/**
* Interface that must be implemented by compilation passes.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
interface CompilerPassInterface
{
/**
* You can modify the container here before it is dumped to PHP code.
*/
public function process(ContainerBuilder $container);
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
use Symfony\Component\DependencyInjection\Reference;
/**
* Checks the validity of references.
*
* The following checks are performed by this pass:
* - target definitions are not abstract
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class CheckReferenceValidityPass extends AbstractRecursivePass
{
protected function processValue($value, bool $isRoot = false)
{
if ($isRoot && $value instanceof Definition && ($value->isSynthetic() || $value->isAbstract())) {
return $value;
}
if ($value instanceof Reference && $this->container->hasDefinition((string) $value)) {
$targetDefinition = $this->container->getDefinition((string) $value);
if ($targetDefinition->isAbstract()) {
throw new RuntimeException(sprintf('The definition "%s" has a reference to an abstract definition "%s". Abstract definitions cannot be the target of references.', $this->currentId, $value));
}
}
return parent::processValue($value, $isRoot);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Alias;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
/**
* Sets a service to be an alias of another one, given a format pattern.
*/
class AutoAliasServicePass implements CompilerPassInterface
{
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container)
{
foreach ($container->findTaggedServiceIds('auto_alias') as $serviceId => $tags) {
foreach ($tags as $tag) {
if (!isset($tag['format'])) {
throw new InvalidArgumentException(sprintf('Missing tag information "format" on auto_alias service "%s".', $serviceId));
}
$aliasId = $container->getParameterBag()->resolveValue($tag['format']);
if ($container->hasDefinition($aliasId) || $container->hasAlias($aliasId)) {
$container->setAlias($serviceId, new Alias($aliasId, true));
}
}
}
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\TypedReference;
use Symfony\Contracts\Service\Attribute\Required;
/**
* Looks for definitions with autowiring enabled and registers their corresponding "@required" properties.
*
* @author Sebastien Morel (Plopix) <morel.seb@gmail.com>
* @author Nicolas Grekas <p@tchwork.com>
*/
class AutowireRequiredPropertiesPass extends AbstractRecursivePass
{
/**
* {@inheritdoc}
*/
protected function processValue($value, bool $isRoot = false)
{
if (\PHP_VERSION_ID < 70400) {
return $value;
}
$value = parent::processValue($value, $isRoot);
if (!$value instanceof Definition || !$value->isAutowired() || $value->isAbstract() || !$value->getClass()) {
return $value;
}
if (!$reflectionClass = $this->container->getReflectionClass($value->getClass(), false)) {
return $value;
}
$properties = $value->getProperties();
foreach ($reflectionClass->getProperties() as $reflectionProperty) {
if (!($type = $reflectionProperty->getType()) instanceof \ReflectionNamedType) {
continue;
}
if ((\PHP_VERSION_ID < 80000 || !$reflectionProperty->getAttributes(Required::class))
&& ((false === $doc = $reflectionProperty->getDocComment()) || false === stripos($doc, '@required') || !preg_match('#(?:^/\*\*|\n\s*+\*)\s*+@required(?:\s|\*/$)#i', $doc))
) {
continue;
}
if (\array_key_exists($name = $reflectionProperty->getName(), $properties)) {
continue;
}
$type = $type->getName();
$value->setProperty($name, new TypedReference($type, $type, ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, $name));
}
return $value;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\Config\Definition\BaseNode;
use Symfony\Component\Config\Definition\ConfigurationInterface;
use Symfony\Component\Config\Definition\Processor;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Extension\ConfigurationExtensionInterface;
use Symfony\Component\DependencyInjection\ParameterBag\EnvPlaceholderParameterBag;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
/**
* Validates environment variable placeholders used in extension configuration with dummy values.
*
* @author Roland Franssen <franssen.roland@gmail.com>
*/
class ValidateEnvPlaceholdersPass implements CompilerPassInterface
{
private static $typeFixtures = ['array' => [], 'bool' => false, 'float' => 0.0, 'int' => 0, 'string' => ''];
private $extensionConfig = [];
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container)
{
$this->extensionConfig = [];
if (!class_exists(BaseNode::class) || !$extensions = $container->getExtensions()) {
return;
}
$resolvingBag = $container->getParameterBag();
if (!$resolvingBag instanceof EnvPlaceholderParameterBag) {
return;
}
$defaultBag = new ParameterBag($resolvingBag->all());
$envTypes = $resolvingBag->getProvidedTypes();
try {
foreach ($resolvingBag->getEnvPlaceholders() + $resolvingBag->getUnusedEnvPlaceholders() as $env => $placeholders) {
$values = [];
if (false === $i = strpos($env, ':')) {
$default = $defaultBag->has("env($env)") ? $defaultBag->get("env($env)") : self::$typeFixtures['string'];
$defaultType = null !== $default ? get_debug_type($default) : 'string';
$values[$defaultType] = $default;
} else {
$prefix = substr($env, 0, $i);
foreach ($envTypes[$prefix] ?? ['string'] as $type) {
$values[$type] = self::$typeFixtures[$type] ?? null;
}
}
foreach ($placeholders as $placeholder) {
BaseNode::setPlaceholder($placeholder, $values);
}
}
$processor = new Processor();
foreach ($extensions as $name => $extension) {
if (!($extension instanceof ConfigurationExtensionInterface || $extension instanceof ConfigurationInterface)
|| !$config = array_filter($container->getExtensionConfig($name))
) {
// this extension has no semantic configuration or was not called
continue;
}
$config = $resolvingBag->resolveValue($config);
if ($extension instanceof ConfigurationInterface) {
$configuration = $extension;
} elseif (null === $configuration = $extension->getConfiguration($config, $container)) {
continue;
}
$this->extensionConfig[$name] = $processor->processConfiguration($configuration, $config);
}
} finally {
BaseNode::resetPlaceholders();
}
$resolvingBag->clearUnusedEnvPlaceholders();
}
/**
* @internal
*/
public function getExtensionConfig(): array
{
try {
return $this->extensionConfig;
} finally {
$this->extensionConfig = [];
}
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Argument\ArgumentInterface;
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
/**
* Run this pass before passes that need to know more about the relation of
* your services.
*
* This class will populate the ServiceReferenceGraph with information. You can
* retrieve the graph in other passes from the compiler.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
* @author Nicolas Grekas <p@tchwork.com>
*/
class AnalyzeServiceReferencesPass extends AbstractRecursivePass
{
private $graph;
private $currentDefinition;
private $onlyConstructorArguments;
private $hasProxyDumper;
private $lazy;
private $byConstructor;
private $byFactory;
private $definitions;
private $aliases;
/**
* @param bool $onlyConstructorArguments Sets this Service Reference pass to ignore method calls
*/
public function __construct(bool $onlyConstructorArguments = false, bool $hasProxyDumper = true)
{
$this->onlyConstructorArguments = $onlyConstructorArguments;
$this->hasProxyDumper = $hasProxyDumper;
$this->enableExpressionProcessing();
}
/**
* Processes a ContainerBuilder object to populate the service reference graph.
*/
public function process(ContainerBuilder $container)
{
$this->container = $container;
$this->graph = $container->getCompiler()->getServiceReferenceGraph();
$this->graph->clear();
$this->lazy = false;
$this->byConstructor = false;
$this->byFactory = false;
$this->definitions = $container->getDefinitions();
$this->aliases = $container->getAliases();
foreach ($this->aliases as $id => $alias) {
$targetId = $this->getDefinitionId((string) $alias);
$this->graph->connect($id, $alias, $targetId, null !== $targetId ? $this->container->getDefinition($targetId) : null, null);
}
try {
parent::process($container);
} finally {
$this->aliases = $this->definitions = [];
}
}
protected function processValue($value, bool $isRoot = false)
{
$lazy = $this->lazy;
$inExpression = $this->inExpression();
if ($value instanceof ArgumentInterface) {
$this->lazy = !$this->byFactory || !$value instanceof IteratorArgument;
parent::processValue($value->getValues());
$this->lazy = $lazy;
return $value;
}
if ($value instanceof Reference) {
$targetId = $this->getDefinitionId((string) $value);
$targetDefinition = null !== $targetId ? $this->container->getDefinition($targetId) : null;
$this->graph->connect(
$this->currentId,
$this->currentDefinition,
$targetId,
$targetDefinition,
$value,
$this->lazy || ($this->hasProxyDumper && $targetDefinition && $targetDefinition->isLazy()),
ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE === $value->getInvalidBehavior(),
$this->byConstructor
);
if ($inExpression) {
$this->graph->connect(
'.internal.reference_in_expression',
null,
$targetId,
$targetDefinition,
$value,
$this->lazy || ($targetDefinition && $targetDefinition->isLazy()),
true
);
}
return $value;
}
if (!$value instanceof Definition) {
return parent::processValue($value, $isRoot);
}
if ($isRoot) {
if ($value->isSynthetic() || $value->isAbstract()) {
return $value;
}
$this->currentDefinition = $value;
} elseif ($this->currentDefinition === $value) {
return $value;
}
$this->lazy = false;
$byConstructor = $this->byConstructor;
$this->byConstructor = $isRoot || $byConstructor;
$byFactory = $this->byFactory;
$this->byFactory = true;
$this->processValue($value->getFactory());
$this->byFactory = $byFactory;
$this->processValue($value->getArguments());
$properties = $value->getProperties();
$setters = $value->getMethodCalls();
// Any references before a "wither" are part of the constructor-instantiation graph
$lastWitherIndex = null;
foreach ($setters as $k => $call) {
if ($call[2] ?? false) {
$lastWitherIndex = $k;
}
}
if (null !== $lastWitherIndex) {
$this->processValue($properties);
$setters = $properties = [];
foreach ($value->getMethodCalls() as $k => $call) {
if (null === $lastWitherIndex) {
$setters[] = $call;
continue;
}
if ($lastWitherIndex === $k) {
$lastWitherIndex = null;
}
$this->processValue($call);
}
}
$this->byConstructor = $byConstructor;
if (!$this->onlyConstructorArguments) {
$this->processValue($properties);
$this->processValue($setters);
$this->processValue($value->getConfigurator());
}
$this->lazy = $lazy;
return $value;
}
private function getDefinitionId(string $id): ?string
{
while (isset($this->aliases[$id])) {
$id = (string) $this->aliases[$id];
}
return isset($this->definitions[$id]) ? $id : null;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Argument\ArgumentInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException;
use Symfony\Component\DependencyInjection\Reference;
/**
* Inline service definitions where this is possible.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class InlineServiceDefinitionsPass extends AbstractRecursivePass
{
private $analyzingPass;
private $cloningIds = [];
private $connectedIds = [];
private $notInlinedIds = [];
private $inlinedIds = [];
private $notInlinableIds = [];
private $graph;
public function __construct(AnalyzeServiceReferencesPass $analyzingPass = null)
{
$this->analyzingPass = $analyzingPass;
}
public function process(ContainerBuilder $container)
{
$this->container = $container;
if ($this->analyzingPass) {
$analyzedContainer = new ContainerBuilder();
$analyzedContainer->setAliases($container->getAliases());
$analyzedContainer->setDefinitions($container->getDefinitions());
foreach ($container->getExpressionLanguageProviders() as $provider) {
$analyzedContainer->addExpressionLanguageProvider($provider);
}
} else {
$analyzedContainer = $container;
}
try {
$remainingInlinedIds = [];
$this->connectedIds = $this->notInlinedIds = $container->getDefinitions();
do {
if ($this->analyzingPass) {
$analyzedContainer->setDefinitions(array_intersect_key($analyzedContainer->getDefinitions(), $this->connectedIds));
$this->analyzingPass->process($analyzedContainer);
}
$this->graph = $analyzedContainer->getCompiler()->getServiceReferenceGraph();
$notInlinedIds = $this->notInlinedIds;
$this->connectedIds = $this->notInlinedIds = $this->inlinedIds = [];
foreach ($analyzedContainer->getDefinitions() as $id => $definition) {
if (!$this->graph->hasNode($id)) {
continue;
}
foreach ($this->graph->getNode($id)->getOutEdges() as $edge) {
if (isset($notInlinedIds[$edge->getSourceNode()->getId()])) {
$this->currentId = $id;
$this->processValue($definition, true);
break;
}
}
}
foreach ($this->inlinedIds as $id => $isPublicOrNotShared) {
if ($isPublicOrNotShared) {
$remainingInlinedIds[$id] = $id;
} else {
$container->removeDefinition($id);
$analyzedContainer->removeDefinition($id);
}
}
} while ($this->inlinedIds && $this->analyzingPass);
foreach ($remainingInlinedIds as $id) {
if (isset($this->notInlinableIds[$id])) {
continue;
}
$definition = $container->getDefinition($id);
if (!$definition->isShared() && !$definition->isPublic()) {
$container->removeDefinition($id);
}
}
} finally {
$this->container = null;
$this->connectedIds = $this->notInlinedIds = $this->inlinedIds = [];
$this->notInlinableIds = [];
$this->graph = null;
}
}
/**
* {@inheritdoc}
*/
protected function processValue($value, bool $isRoot = false)
{
if ($value instanceof ArgumentInterface) {
// Reference found in ArgumentInterface::getValues() are not inlineable
return $value;
}
if ($value instanceof Definition && $this->cloningIds) {
if ($value->isShared()) {
return $value;
}
$value = clone $value;
}
if (!$value instanceof Reference) {
return parent::processValue($value, $isRoot);
} elseif (!$this->container->hasDefinition($id = (string) $value)) {
return $value;
}
$definition = $this->container->getDefinition($id);
if (!$this->isInlineableDefinition($id, $definition)) {
$this->notInlinableIds[$id] = true;
return $value;
}
$this->container->log($this, sprintf('Inlined service "%s" to "%s".', $id, $this->currentId));
$this->inlinedIds[$id] = $definition->isPublic() || !$definition->isShared();
$this->notInlinedIds[$this->currentId] = true;
if ($definition->isShared()) {
return $definition;
}
if (isset($this->cloningIds[$id])) {
$ids = array_keys($this->cloningIds);
$ids[] = $id;
throw new ServiceCircularReferenceException($id, \array_slice($ids, array_search($id, $ids)));
}
$this->cloningIds[$id] = true;
try {
return $this->processValue($definition);
} finally {
unset($this->cloningIds[$id]);
}
}
/**
* Checks if the definition is inlineable.
*/
private function isInlineableDefinition(string $id, Definition $definition): bool
{
if ($definition->hasErrors() || $definition->isDeprecated() || $definition->isLazy() || $definition->isSynthetic()) {
return false;
}
if (!$definition->isShared()) {
if (!$this->graph->hasNode($id)) {
return true;
}
foreach ($this->graph->getNode($id)->getInEdges() as $edge) {
$srcId = $edge->getSourceNode()->getId();
$this->connectedIds[$srcId] = true;
if ($edge->isWeak() || $edge->isLazy()) {
return false;
}
}
return true;
}
if ($definition->isPublic()) {
return false;
}
if (!$this->graph->hasNode($id)) {
return true;
}
if ($this->currentId == $id) {
return false;
}
$this->connectedIds[$id] = true;
$srcIds = [];
$srcCount = 0;
$isReferencedByConstructor = false;
foreach ($this->graph->getNode($id)->getInEdges() as $edge) {
$isReferencedByConstructor = $isReferencedByConstructor || $edge->isReferencedByConstructor();
$srcId = $edge->getSourceNode()->getId();
$this->connectedIds[$srcId] = true;
if ($edge->isWeak() || $edge->isLazy()) {
return false;
}
$srcIds[$srcId] = true;
++$srcCount;
}
if (1 !== \count($srcIds)) {
$this->notInlinedIds[$id] = true;
return false;
}
if ($srcCount > 1 && \is_array($factory = $definition->getFactory()) && ($factory[0] instanceof Reference || $factory[0] instanceof Definition)) {
return false;
}
return $this->container->getDefinition($srcId)->isShared();
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
class RegisterReverseContainerPass implements CompilerPassInterface
{
private $beforeRemoving;
private $serviceId;
private $tagName;
public function __construct(bool $beforeRemoving, string $serviceId = 'reverse_container', string $tagName = 'container.reversible')
{
$this->beforeRemoving = $beforeRemoving;
$this->serviceId = $serviceId;
$this->tagName = $tagName;
}
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition($this->serviceId)) {
return;
}
$refType = $this->beforeRemoving ? ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE : ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE;
$services = [];
foreach ($container->findTaggedServiceIds($this->tagName) as $id => $tags) {
$services[$id] = new Reference($id, $refType);
}
if ($this->beforeRemoving) {
// prevent inlining of the reverse container
$services[$this->serviceId] = new Reference($this->serviceId, $refType);
}
$locator = $container->getDefinition($this->serviceId)->getArgument(1);
if ($locator instanceof Reference) {
$locator = $container->getDefinition((string) $locator);
}
if ($locator instanceof Definition) {
foreach ($services as $id => $ref) {
$services[$id] = new ServiceClosureArgument($ref);
}
$locator->replaceArgument(0, $services);
} else {
$locator->setValues($services);
}
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
/**
* Applies instanceof conditionals to definitions.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
class ResolveInstanceofConditionalsPass implements CompilerPassInterface
{
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container)
{
foreach ($container->getAutoconfiguredInstanceof() as $interface => $definition) {
if ($definition->getArguments()) {
throw new InvalidArgumentException(sprintf('Autoconfigured instanceof for type "%s" defines arguments but these are not supported and should be removed.', $interface));
}
}
$tagsToKeep = [];
if ($container->hasParameter('container.behavior_describing_tags')) {
$tagsToKeep = $container->getParameter('container.behavior_describing_tags');
}
foreach ($container->getDefinitions() as $id => $definition) {
$container->setDefinition($id, $this->processDefinition($container, $id, $definition, $tagsToKeep));
}
if ($container->hasParameter('container.behavior_describing_tags')) {
$container->getParameterBag()->remove('container.behavior_describing_tags');
}
}
private function processDefinition(ContainerBuilder $container, string $id, Definition $definition, array $tagsToKeep): Definition
{
$instanceofConditionals = $definition->getInstanceofConditionals();
$autoconfiguredInstanceof = $definition->isAutoconfigured() ? $container->getAutoconfiguredInstanceof() : [];
if (!$instanceofConditionals && !$autoconfiguredInstanceof) {
return $definition;
}
if (!$class = $container->getParameterBag()->resolveValue($definition->getClass())) {
return $definition;
}
$conditionals = $this->mergeConditionals($autoconfiguredInstanceof, $instanceofConditionals, $container);
$definition->setInstanceofConditionals([]);
$shared = null;
$instanceofTags = [];
$instanceofCalls = [];
$instanceofBindings = [];
$reflectionClass = null;
$parent = $definition instanceof ChildDefinition ? $definition->getParent() : null;
foreach ($conditionals as $interface => $instanceofDefs) {
if ($interface !== $class && !(null === $reflectionClass ? $reflectionClass = ($container->getReflectionClass($class, false) ?: false) : $reflectionClass)) {
continue;
}
if ($interface !== $class && !is_subclass_of($class, $interface)) {
continue;
}
foreach ($instanceofDefs as $key => $instanceofDef) {
/** @var ChildDefinition $instanceofDef */
$instanceofDef = clone $instanceofDef;
$instanceofDef->setAbstract(true)->setParent($parent ?: '.abstract.instanceof.'.$id);
$parent = '.instanceof.'.$interface.'.'.$key.'.'.$id;
$container->setDefinition($parent, $instanceofDef);
$instanceofTags[] = $instanceofDef->getTags();
$instanceofBindings = $instanceofDef->getBindings() + $instanceofBindings;
foreach ($instanceofDef->getMethodCalls() as $methodCall) {
$instanceofCalls[] = $methodCall;
}
$instanceofDef->setTags([]);
$instanceofDef->setMethodCalls([]);
$instanceofDef->setBindings([]);
if (isset($instanceofDef->getChanges()['shared'])) {
$shared = $instanceofDef->isShared();
}
}
}
if ($parent) {
$bindings = $definition->getBindings();
$abstract = $container->setDefinition('.abstract.instanceof.'.$id, $definition);
$definition->setBindings([]);
$definition = serialize($definition);
if (Definition::class === \get_class($abstract)) {
// cast Definition to ChildDefinition
$definition = substr_replace($definition, '53', 2, 2);
$definition = substr_replace($definition, 'Child', 44, 0);
}
/** @var ChildDefinition $definition */
$definition = unserialize($definition);
$definition->setParent($parent);
if (null !== $shared && !isset($definition->getChanges()['shared'])) {
$definition->setShared($shared);
}
// Don't add tags to service decorators
$i = \count($instanceofTags);
while (0 <= --$i) {
foreach ($instanceofTags[$i] as $k => $v) {
if (null === $definition->getDecoratedService() || \in_array($k, $tagsToKeep, true)) {
foreach ($v as $v) {
if ($definition->hasTag($k) && \in_array($v, $definition->getTag($k))) {
continue;
}
$definition->addTag($k, $v);
}
}
}
}
$definition->setMethodCalls(array_merge($instanceofCalls, $definition->getMethodCalls()));
$definition->setBindings($bindings + $instanceofBindings);
// reset fields with "merge" behavior
$abstract
->setBindings([])
->setArguments([])
->setMethodCalls([])
->setDecoratedService(null)
->setTags([])
->setAbstract(true);
}
return $definition;
}
private function mergeConditionals(array $autoconfiguredInstanceof, array $instanceofConditionals, ContainerBuilder $container): array
{
// make each value an array of ChildDefinition
$conditionals = array_map(function ($childDef) { return [$childDef]; }, $autoconfiguredInstanceof);
foreach ($instanceofConditionals as $interface => $instanceofDef) {
// make sure the interface/class exists (but don't validate automaticInstanceofConditionals)
if (!$container->getReflectionClass($interface)) {
throw new RuntimeException(sprintf('"%s" is set as an "instanceof" conditional, but it does not exist.', $interface));
}
if (!isset($autoconfiguredInstanceof[$interface])) {
$conditionals[$interface] = [];
}
$conditionals[$interface][] = $instanceofDef;
}
return $conditionals;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\ExceptionInterface;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException;
/**
* This replaces all ChildDefinition instances with their equivalent fully
* merged Definition instance.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
* @author Nicolas Grekas <p@tchwork.com>
*/
class ResolveChildDefinitionsPass extends AbstractRecursivePass
{
private $currentPath;
protected function processValue($value, bool $isRoot = false)
{
if (!$value instanceof Definition) {
return parent::processValue($value, $isRoot);
}
if ($isRoot) {
// yes, we are specifically fetching the definition from the
// container to ensure we are not operating on stale data
$value = $this->container->getDefinition($this->currentId);
}
if ($value instanceof ChildDefinition) {
$this->currentPath = [];
$value = $this->resolveDefinition($value);
if ($isRoot) {
$this->container->setDefinition($this->currentId, $value);
}
}
return parent::processValue($value, $isRoot);
}
/**
* Resolves the definition.
*
* @throws RuntimeException When the definition is invalid
*/
private function resolveDefinition(ChildDefinition $definition): Definition
{
try {
return $this->doResolveDefinition($definition);
} catch (ServiceCircularReferenceException $e) {
throw $e;
} catch (ExceptionInterface $e) {
$r = new \ReflectionProperty($e, 'message');
$r->setAccessible(true);
$r->setValue($e, sprintf('Service "%s": %s', $this->currentId, $e->getMessage()));
throw $e;
}
}
private function doResolveDefinition(ChildDefinition $definition): Definition
{
if (!$this->container->has($parent = $definition->getParent())) {
throw new RuntimeException(sprintf('Parent definition "%s" does not exist.', $parent));
}
$searchKey = array_search($parent, $this->currentPath);
$this->currentPath[] = $parent;
if (false !== $searchKey) {
throw new ServiceCircularReferenceException($parent, \array_slice($this->currentPath, $searchKey));
}
$parentDef = $this->container->findDefinition($parent);
if ($parentDef instanceof ChildDefinition) {
$id = $this->currentId;
$this->currentId = $parent;
$parentDef = $this->resolveDefinition($parentDef);
$this->container->setDefinition($parent, $parentDef);
$this->currentId = $id;
}
$this->container->log($this, sprintf('Resolving inheritance for "%s" (parent: %s).', $this->currentId, $parent));
$def = new Definition();
// merge in parent definition
// purposely ignored attributes: abstract, shared, tags, autoconfigured
$def->setClass($parentDef->getClass());
$def->setArguments($parentDef->getArguments());
$def->setMethodCalls($parentDef->getMethodCalls());
$def->setProperties($parentDef->getProperties());
if ($parentDef->isDeprecated()) {
$deprecation = $parentDef->getDeprecation('%service_id%');
$def->setDeprecated($deprecation['package'], $deprecation['version'], $deprecation['message']);
}
$def->setFactory($parentDef->getFactory());
$def->setConfigurator($parentDef->getConfigurator());
$def->setFile($parentDef->getFile());
$def->setPublic($parentDef->isPublic());
$def->setLazy($parentDef->isLazy());
$def->setAutowired($parentDef->isAutowired());
$def->setChanges($parentDef->getChanges());
$def->setBindings($definition->getBindings() + $parentDef->getBindings());
// overwrite with values specified in the decorator
$changes = $definition->getChanges();
if (isset($changes['class'])) {
$def->setClass($definition->getClass());
}
if (isset($changes['factory'])) {
$def->setFactory($definition->getFactory());
}
if (isset($changes['configurator'])) {
$def->setConfigurator($definition->getConfigurator());
}
if (isset($changes['file'])) {
$def->setFile($definition->getFile());
}
if (isset($changes['public'])) {
$def->setPublic($definition->isPublic());
} else {
$def->setPublic($parentDef->isPublic());
}
if (isset($changes['lazy'])) {
$def->setLazy($definition->isLazy());
}
if (isset($changes['deprecated'])) {
if ($definition->isDeprecated()) {
$deprecation = $definition->getDeprecation('%service_id%');
$def->setDeprecated($deprecation['package'], $deprecation['version'], $deprecation['message']);
} else {
$def->setDeprecated(false);
}
}
if (isset($changes['autowired'])) {
$def->setAutowired($definition->isAutowired());
}
if (isset($changes['shared'])) {
$def->setShared($definition->isShared());
}
if (isset($changes['decorated_service'])) {
$decoratedService = $definition->getDecoratedService();
if (null === $decoratedService) {
$def->setDecoratedService($decoratedService);
} else {
$def->setDecoratedService($decoratedService[0], $decoratedService[1], $decoratedService[2], $decoratedService[3] ?? ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE);
}
}
// merge arguments
foreach ($definition->getArguments() as $k => $v) {
if (is_numeric($k)) {
$def->addArgument($v);
} elseif (0 === strpos($k, 'index_')) {
$def->replaceArgument((int) substr($k, \strlen('index_')), $v);
} else {
$def->setArgument($k, $v);
}
}
// merge properties
foreach ($definition->getProperties() as $k => $v) {
$def->setProperty($k, $v);
}
// append method calls
if ($calls = $definition->getMethodCalls()) {
$def->setMethodCalls(array_merge($def->getMethodCalls(), $calls));
}
$def->addError($parentDef);
$def->addError($definition);
// these attributes are always taken from the child
$def->setAbstract($definition->isAbstract());
$def->setTags($definition->getTags());
// autoconfigure is never taken from parent (on purpose)
// and it's not legal on an instanceof
$def->setAutoconfigured($definition->isAutoconfigured());
return $def;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException;
/**
* Resolves all parameter placeholders "%somevalue%" to their real values.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class ResolveParameterPlaceHoldersPass extends AbstractRecursivePass
{
private $bag;
private $resolveArrays;
private $throwOnResolveException;
public function __construct($resolveArrays = true, $throwOnResolveException = true)
{
$this->resolveArrays = $resolveArrays;
$this->throwOnResolveException = $throwOnResolveException;
}
/**
* {@inheritdoc}
*
* @throws ParameterNotFoundException
*/
public function process(ContainerBuilder $container)
{
$this->bag = $container->getParameterBag();
try {
parent::process($container);
$aliases = [];
foreach ($container->getAliases() as $name => $target) {
$this->currentId = $name;
$aliases[$this->bag->resolveValue($name)] = $target;
}
$container->setAliases($aliases);
} catch (ParameterNotFoundException $e) {
$e->setSourceId($this->currentId);
throw $e;
}
$this->bag->resolve();
$this->bag = null;
}
protected function processValue($value, bool $isRoot = false)
{
if (\is_string($value)) {
try {
$v = $this->bag->resolveValue($value);
} catch (ParameterNotFoundException $e) {
if ($this->throwOnResolveException) {
throw $e;
}
$v = null;
$this->container->getDefinition($this->currentId)->addError($e->getMessage());
}
return $this->resolveArrays || !$v || !\is_array($v) ? $v : $value;
}
if ($value instanceof Definition) {
$value->setBindings($this->processValue($value->getBindings()));
$changes = $value->getChanges();
if (isset($changes['class'])) {
$value->setClass($this->bag->resolveValue($value->getClass()));
}
if (isset($changes['file'])) {
$value->setFile($this->bag->resolveValue($value->getFile()));
}
}
$value = parent::processValue($value, $isRoot);
if ($value && \is_array($value)) {
$value = array_combine($this->bag->resolveValue(array_keys($value)), $value);
}
return $value;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Reference;
final class AliasDeprecatedPublicServicesPass extends AbstractRecursivePass
{
private $tagName;
private $aliases = [];
public function __construct(string $tagName = 'container.private')
{
$this->tagName = $tagName;
}
/**
* {@inheritdoc}
*/
protected function processValue($value, bool $isRoot = false)
{
if ($value instanceof Reference && isset($this->aliases[$id = (string) $value])) {
return new Reference($this->aliases[$id], $value->getInvalidBehavior());
}
return parent::processValue($value, $isRoot);
}
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container)
{
foreach ($container->findTaggedServiceIds($this->tagName) as $id => $tags) {
if (null === $package = $tags[0]['package'] ?? null) {
throw new InvalidArgumentException(sprintf('The "package" attribute is mandatory for the "%s" tag on the "%s" service.', $this->tagName, $id));
}
if (null === $version = $tags[0]['version'] ?? null) {
throw new InvalidArgumentException(sprintf('The "version" attribute is mandatory for the "%s" tag on the "%s" service.', $this->tagName, $id));
}
$definition = $container->getDefinition($id);
if (!$definition->isPublic() || $definition->isPrivate()) {
throw new InvalidArgumentException(sprintf('The "%s" service is private: it cannot have the "%s" tag.', $id, $this->tagName));
}
$container
->setAlias($id, $aliasId = '.'.$this->tagName.'.'.$id)
->setPublic(true)
->setDeprecated($package, $version, 'Accessing the "%alias_id%" service directly from the container is deprecated, use dependency injection instead.');
$container->setDefinition($aliasId, $definition);
$this->aliases[$id] = $aliasId;
}
parent::process($container);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Definition;
/**
* Replaces env var placeholders by their current values.
*/
class ResolveEnvPlaceholdersPass extends AbstractRecursivePass
{
protected function processValue($value, bool $isRoot = false)
{
if (\is_string($value)) {
return $this->container->resolveEnvPlaceholders($value, true);
}
if ($value instanceof Definition) {
$changes = $value->getChanges();
if (isset($changes['class'])) {
$value->setClass($this->container->resolveEnvPlaceholders($value->getClass(), true));
}
if (isset($changes['file'])) {
$value->setFile($this->container->resolveEnvPlaceholders($value->getFile(), true));
}
}
$value = parent::processValue($value, $isRoot);
if ($value && \is_array($value) && !$isRoot) {
$value = array_combine($this->container->resolveEnvPlaceholders(array_keys($value), true), $value);
}
return $value;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Argument\AbstractArgument;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\LazyProxy\ProxyHelper;
use Symfony\Component\DependencyInjection\Reference;
/**
* Resolves named arguments to their corresponding numeric index.
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
class ResolveNamedArgumentsPass extends AbstractRecursivePass
{
/**
* {@inheritdoc}
*/
protected function processValue($value, bool $isRoot = false)
{
if ($value instanceof AbstractArgument && $value->getText().'.' === $value->getTextWithContext()) {
$value->setContext(sprintf('A value found in service "%s"', $this->currentId));
}
if (!$value instanceof Definition) {
return parent::processValue($value, $isRoot);
}
$calls = $value->getMethodCalls();
$calls[] = ['__construct', $value->getArguments()];
foreach ($calls as $i => $call) {
[$method, $arguments] = $call;
$parameters = null;
$resolvedArguments = [];
foreach ($arguments as $key => $argument) {
if ($argument instanceof AbstractArgument && $argument->getText().'.' === $argument->getTextWithContext()) {
$argument->setContext(sprintf('Argument '.(\is_int($key) ? 1 + $key : '"%3$s"').' of '.('__construct' === $method ? 'service "%s"' : 'method call "%s::%s()"'), $this->currentId, $method, $key));
}
if (\is_int($key)) {
$resolvedArguments[$key] = $argument;
continue;
}
if (null === $parameters) {
$r = $this->getReflectionMethod($value, $method);
$class = $r instanceof \ReflectionMethod ? $r->class : $this->currentId;
$method = $r->getName();
$parameters = $r->getParameters();
}
if (isset($key[0]) && '$' !== $key[0] && !class_exists($key) && !interface_exists($key, false)) {
throw new InvalidArgumentException(sprintf('Invalid service "%s": did you forget to add the "$" prefix to argument "%s"?', $this->currentId, $key));
}
if (isset($key[0]) && '$' === $key[0]) {
foreach ($parameters as $j => $p) {
if ($key === '$'.$p->name) {
if ($p->isVariadic() && \is_array($argument)) {
foreach ($argument as $variadicArgument) {
$resolvedArguments[$j++] = $variadicArgument;
}
} else {
$resolvedArguments[$j] = $argument;
}
continue 2;
}
}
throw new InvalidArgumentException(sprintf('Invalid service "%s": method "%s()" has no argument named "%s". Check your service definition.', $this->currentId, $class !== $this->currentId ? $class.'::'.$method : $method, $key));
}
if (null !== $argument && !$argument instanceof Reference && !$argument instanceof Definition) {
throw new InvalidArgumentException(sprintf('Invalid service "%s": the value of argument "%s" of method "%s()" must be null, an instance of "%s" or an instance of "%s", "%s" given.', $this->currentId, $key, $class !== $this->currentId ? $class.'::'.$method : $method, Reference::class, Definition::class, get_debug_type($argument)));
}
$typeFound = false;
foreach ($parameters as $j => $p) {
if (!\array_key_exists($j, $resolvedArguments) && ProxyHelper::getTypeHint($r, $p, true) === $key) {
$resolvedArguments[$j] = $argument;
$typeFound = true;
}
}
if (!$typeFound) {
throw new InvalidArgumentException(sprintf('Invalid service "%s": method "%s()" has no argument type-hinted as "%s". Check your service definition.', $this->currentId, $class !== $this->currentId ? $class.'::'.$method : $method, $key));
}
}
if ($resolvedArguments !== $call[1]) {
ksort($resolvedArguments);
$calls[$i][1] = $resolvedArguments;
}
}
[, $arguments] = array_pop($calls);
if ($arguments !== $value->getArguments()) {
$value->setArguments($arguments);
}
if ($calls !== $value->getMethodCalls()) {
$value->setMethodCalls($calls);
}
foreach ($value->getProperties() as $key => $argument) {
if ($argument instanceof AbstractArgument && $argument->getText().'.' === $argument->getTextWithContext()) {
$argument->setContext(sprintf('Property "%s" of service "%s"', $key, $this->currentId));
}
}
return parent::processValue($value, $isRoot);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\Argument\RewindableGenerator;
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Exception\InvalidParameterTypeException;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
use Symfony\Component\DependencyInjection\ExpressionLanguage;
use Symfony\Component\DependencyInjection\Parameter;
use Symfony\Component\DependencyInjection\ParameterBag\EnvPlaceholderParameterBag;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\ServiceLocator;
use Symfony\Component\ExpressionLanguage\Expression;
/**
* Checks whether injected parameters are compatible with type declarations.
*
* This pass should be run after all optimization passes.
*
* It can be added either:
* * before removing passes to check all services even if they are not currently used,
* * after removing passes to check only services are used in the app.
*
* @author Nicolas Grekas <p@tchwork.com>
* @author Julien Maulny <jmaulny@darkmira.fr>
*/
final class CheckTypeDeclarationsPass extends AbstractRecursivePass
{
private const SCALAR_TYPES = [
'int' => true,
'float' => true,
'bool' => true,
'string' => true,
];
private const BUILTIN_TYPES = [
'array' => true,
'bool' => true,
'callable' => true,
'float' => true,
'int' => true,
'iterable' => true,
'object' => true,
'string' => true,
];
private $autoload;
private $skippedIds;
private $expressionLanguage;
/**
* @param bool $autoload Whether services who's class in not loaded should be checked or not.
* Defaults to false to save loading code during compilation.
* @param array $skippedIds An array indexed by the service ids to skip
*/
public function __construct(bool $autoload = false, array $skippedIds = [])
{
$this->autoload = $autoload;
$this->skippedIds = $skippedIds;
}
/**
* {@inheritdoc}
*/
protected function processValue($value, $isRoot = false)
{
if (isset($this->skippedIds[$this->currentId])) {
return $value;
}
if (!$value instanceof Definition || $value->hasErrors()) {
return parent::processValue($value, $isRoot);
}
if (!$this->autoload && !class_exists($class = $value->getClass(), false) && !interface_exists($class, false)) {
return parent::processValue($value, $isRoot);
}
if (ServiceLocator::class === $value->getClass()) {
return parent::processValue($value, $isRoot);
}
if ($constructor = $this->getConstructor($value, false)) {
$this->checkTypeDeclarations($value, $constructor, $value->getArguments());
}
foreach ($value->getMethodCalls() as $methodCall) {
try {
$reflectionMethod = $this->getReflectionMethod($value, $methodCall[0]);
} catch (RuntimeException $e) {
if ($value->getFactory()) {
continue;
}
throw $e;
}
$this->checkTypeDeclarations($value, $reflectionMethod, $methodCall[1]);
}
return parent::processValue($value, $isRoot);
}
/**
* @throws InvalidArgumentException When not enough parameters are defined for the method
*/
private function checkTypeDeclarations(Definition $checkedDefinition, \ReflectionFunctionAbstract $reflectionFunction, array $values): void
{
$numberOfRequiredParameters = $reflectionFunction->getNumberOfRequiredParameters();
if (\count($values) < $numberOfRequiredParameters) {
throw new InvalidArgumentException(sprintf('Invalid definition for service "%s": "%s::%s()" requires %d arguments, %d passed.', $this->currentId, $reflectionFunction->class, $reflectionFunction->name, $numberOfRequiredParameters, \count($values)));
}
$reflectionParameters = $reflectionFunction->getParameters();
$checksCount = min($reflectionFunction->getNumberOfParameters(), \count($values));
$envPlaceholderUniquePrefix = $this->container->getParameterBag() instanceof EnvPlaceholderParameterBag ? $this->container->getParameterBag()->getEnvPlaceholderUniquePrefix() : null;
for ($i = 0; $i < $checksCount; ++$i) {
if (!$reflectionParameters[$i]->hasType() || $reflectionParameters[$i]->isVariadic()) {
continue;
}
$this->checkType($checkedDefinition, $values[$i], $reflectionParameters[$i], $envPlaceholderUniquePrefix);
}
if ($reflectionFunction->isVariadic() && ($lastParameter = end($reflectionParameters))->hasType()) {
$variadicParameters = \array_slice($values, $lastParameter->getPosition());
foreach ($variadicParameters as $variadicParameter) {
$this->checkType($checkedDefinition, $variadicParameter, $lastParameter, $envPlaceholderUniquePrefix);
}
}
}
/**
* @throws InvalidParameterTypeException When a parameter is not compatible with the declared type
*/
private function checkType(Definition $checkedDefinition, $value, \ReflectionParameter $parameter, ?string $envPlaceholderUniquePrefix, string $type = null): void
{
if (null === $type) {
$type = $parameter->getType();
if ($type instanceof \ReflectionUnionType) {
foreach ($type->getTypes() as $type) {
try {
$this->checkType($checkedDefinition, $value, $parameter, $envPlaceholderUniquePrefix, $type);
return;
} catch (InvalidParameterTypeException $e) {
}
}
throw new InvalidParameterTypeException($this->currentId, $e->getCode(), $parameter);
}
$type = $type->getName();
}
if ($value instanceof Reference) {
if (!$this->container->has($value = (string) $value)) {
return;
}
if ('service_container' === $value && is_a($type, Container::class, true)) {
return;
}
$value = $this->container->findDefinition($value);
}
if ('self' === $type) {
$type = $parameter->getDeclaringClass()->getName();
}
if ('static' === $type) {
$type = $checkedDefinition->getClass();
}
$class = null;
if ($value instanceof Definition) {
$class = $value->getClass();
if (isset(self::BUILTIN_TYPES[strtolower($class)])) {
$class = strtolower($class);
} elseif (!$class || (!$this->autoload && !class_exists($class, false) && !interface_exists($class, false))) {
return;
}
} elseif ($value instanceof Parameter) {
$value = $this->container->getParameter($value);
} elseif ($value instanceof Expression) {
try {
$value = $this->getExpressionLanguage()->evaluate($value, ['container' => $this->container]);
} catch (\Exception $e) {
// If a service from the expression cannot be fetched from the container, we skip the validation.
return;
}
} elseif (\is_string($value)) {
if ('%' === ($value[0] ?? '') && preg_match('/^%([^%]+)%$/', $value, $match)) {
$value = $this->container->getParameter(substr($value, 1, -1));
}
if ($envPlaceholderUniquePrefix && \is_string($value) && false !== strpos($value, 'env_')) {
// If the value is an env placeholder that is either mixed with a string or with another env placeholder, then its resolved value will always be a string, so we don't need to resolve it.
// We don't need to change the value because it is already a string.
if ('' === preg_replace('/'.$envPlaceholderUniquePrefix.'_\w+_[a-f0-9]{32}/U', '', $value, -1, $c) && 1 === $c) {
try {
$value = $this->container->resolveEnvPlaceholders($value, true);
} catch (\Exception $e) {
// If an env placeholder cannot be resolved, we skip the validation.
return;
}
}
}
}
if (null === $value && $parameter->allowsNull()) {
return;
}
if (null === $class) {
if ($value instanceof IteratorArgument) {
$class = RewindableGenerator::class;
} elseif ($value instanceof ServiceClosureArgument) {
$class = \Closure::class;
} elseif ($value instanceof ServiceLocatorArgument) {
$class = ServiceLocator::class;
} elseif (\is_object($value)) {
$class = \get_class($value);
} else {
$class = \gettype($value);
$class = ['integer' => 'int', 'double' => 'float', 'boolean' => 'bool'][$class] ?? $class;
}
}
if (isset(self::SCALAR_TYPES[$type]) && isset(self::SCALAR_TYPES[$class])) {
return;
}
if ('string' === $type && method_exists($class, '__toString')) {
return;
}
if ('callable' === $type && (\Closure::class === $class || method_exists($class, '__invoke'))) {
return;
}
if ('callable' === $type && \is_array($value) && isset($value[0]) && ($value[0] instanceof Reference || $value[0] instanceof Definition || \is_string($value[0]))) {
return;
}
if ('iterable' === $type && (\is_array($value) || 'array' === $class || is_subclass_of($class, \Traversable::class))) {
return;
}
if ($type === $class) {
return;
}
if ('object' === $type && !isset(self::BUILTIN_TYPES[$class])) {
return;
}
if (is_a($class, $type, true)) {
return;
}
$checkFunction = sprintf('is_%s', $type);
if (!$parameter->getType()->isBuiltin() || !$checkFunction($value)) {
throw new InvalidParameterTypeException($this->currentId, \is_object($value) ? $class : get_debug_type($value), $parameter);
}
}
private function getExpressionLanguage(): ExpressionLanguage
{
if (null === $this->expressionLanguage) {
$this->expressionLanguage = new ExpressionLanguage(null, $this->container->getExpressionLanguageProviders());
}
return $this->expressionLanguage;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Alias;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
use Symfony\Component\DependencyInjection\Reference;
/**
* Overwrites a service but keeps the overridden one.
*
* @author Christophe Coevoet <stof@notk.org>
* @author Fabien Potencier <fabien@symfony.com>
* @author Diego Saint Esteben <diego@saintesteben.me>
*/
class DecoratorServicePass extends AbstractRecursivePass
{
private $innerId = '.inner';
public function __construct(?string $innerId = '.inner')
{
$this->innerId = $innerId;
}
public function process(ContainerBuilder $container)
{
$definitions = new \SplPriorityQueue();
$order = \PHP_INT_MAX;
foreach ($container->getDefinitions() as $id => $definition) {
if (!$decorated = $definition->getDecoratedService()) {
continue;
}
$definitions->insert([$id, $definition], [$decorated[2], --$order]);
}
$decoratingDefinitions = [];
foreach ($definitions as [$id, $definition]) {
$decoratedService = $definition->getDecoratedService();
[$inner, $renamedId] = $decoratedService;
$invalidBehavior = $decoratedService[3] ?? ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE;
$definition->setDecoratedService(null);
if (!$renamedId) {
$renamedId = $id.'.inner';
}
$this->currentId = $renamedId;
$this->processValue($definition);
$definition->innerServiceId = $renamedId;
$definition->decorationOnInvalid = $invalidBehavior;
// we create a new alias/service for the service we are replacing
// to be able to reference it in the new one
if ($container->hasAlias($inner)) {
$alias = $container->getAlias($inner);
$public = $alias->isPublic();
$private = $alias->isPrivate();
$container->setAlias($renamedId, new Alias((string) $alias, false));
} elseif ($container->hasDefinition($inner)) {
$decoratedDefinition = $container->getDefinition($inner);
$public = $decoratedDefinition->isPublic();
$private = $decoratedDefinition->isPrivate();
$decoratedDefinition->setPublic(false);
$container->setDefinition($renamedId, $decoratedDefinition);
$decoratingDefinitions[$inner] = $decoratedDefinition;
} elseif (ContainerInterface::IGNORE_ON_INVALID_REFERENCE === $invalidBehavior) {
$container->removeDefinition($id);
continue;
} elseif (ContainerInterface::NULL_ON_INVALID_REFERENCE === $invalidBehavior) {
$public = $definition->isPublic();
$private = $definition->isPrivate();
} else {
throw new ServiceNotFoundException($inner, $id);
}
if (isset($decoratingDefinitions[$inner])) {
$decoratingDefinition = $decoratingDefinitions[$inner];
$decoratingTags = $decoratingDefinition->getTags();
$resetTags = [];
if (isset($decoratingTags['container.service_locator'])) {
// container.service_locator has special logic and it must not be transferred out to decorators
$resetTags = ['container.service_locator' => $decoratingTags['container.service_locator']];
unset($decoratingTags['container.service_locator']);
}
$definition->setTags(array_merge($decoratingTags, $definition->getTags()));
$decoratingDefinition->setTags($resetTags);
$decoratingDefinitions[$inner] = $definition;
}
$container->setAlias($inner, $id)->setPublic($public);
}
}
protected function processValue($value, bool $isRoot = false)
{
if ($value instanceof Reference && $this->innerId === (string) $value) {
return new Reference($this->currentId, $value->getInvalidBehavior());
}
return parent::processValue($value, $isRoot);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
/**
* The EnvVarProcessorInterface is implemented by objects that manage environment-like variables.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
interface EnvVarProcessorInterface
{
/**
* Returns the value of the given variable as managed by the current instance.
*
* @param string $prefix The namespace of the variable
* @param string $name The name of the variable within the namespace
* @param \Closure $getEnv A closure that allows fetching more env vars
*
* @return mixed
*
* @throws RuntimeException on error
*/
public function getEnv(string $prefix, string $name, \Closure $getEnv);
/**
* @return string[] The PHP-types managed by getEnv(), keyed by prefixes
*/
public static function getProvidedTypes();
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection;
/**
* TaggedContainerInterface is the interface implemented when a container knows how to deals with tags.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
interface TaggedContainerInterface extends ContainerInterface
{
/**
* Returns service ids for a given tag.
*
* @param string $name The tag name
*
* @return array An array of tags
*/
public function findTaggedServiceIds(string $name);
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\LazyProxy\Instantiator;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Definition;
/**
* Lazy proxy instantiator, capable of instantiating a proxy given a container, the
* service definitions and a callback that produces the real service instance.
*
* @author Marco Pivetta <ocramius@gmail.com>
*/
interface InstantiatorInterface
{
/**
* Instantiates a proxy object.
*
* @param string $id Identifier of the requested service
* @param callable $realInstantiator Zero-argument callback that is capable of producing the real service instance
*
* @return object
*/
public function instantiateProxy(ContainerInterface $container, Definition $definition, string $id, callable $realInstantiator);
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\LazyProxy\Instantiator;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Definition;
/**
* {@inheritdoc}
*
* Noop proxy instantiator - produces the real service instead of a proxy instance.
*
* @author Marco Pivetta <ocramius@gmail.com>
*/
class RealServiceInstantiator implements InstantiatorInterface
{
/**
* {@inheritdoc}
*/
public function instantiateProxy(ContainerInterface $container, Definition $definition, string $id, callable $realInstantiator)
{
return $realInstantiator();
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\LazyProxy\PhpDumper;
use Symfony\Component\DependencyInjection\Definition;
/**
* Null dumper, negates any proxy code generation for any given service definition.
*
* @author Marco Pivetta <ocramius@gmail.com>
*
* @final
*/
class NullDumper implements DumperInterface
{
/**
* {@inheritdoc}
*/
public function isProxyCandidate(Definition $definition): bool
{
return false;
}
/**
* {@inheritdoc}
*/
public function getProxyFactoryCode(Definition $definition, string $id, string $factoryCode): string
{
return '';
}
/**
* {@inheritdoc}
*/
public function getProxyCode(Definition $definition): string
{
return '';
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\LazyProxy\PhpDumper;
use Symfony\Component\DependencyInjection\Definition;
/**
* Lazy proxy dumper capable of generating the instantiation logic PHP code for proxied services.
*
* @author Marco Pivetta <ocramius@gmail.com>
*/
interface DumperInterface
{
/**
* Inspects whether the given definitions should produce proxy instantiation logic in the dumped container.
*
* @return bool
*/
public function isProxyCandidate(Definition $definition);
/**
* Generates the code to be used to instantiate a proxy in the dumped factory code.
*
* @return string
*/
public function getProxyFactoryCode(Definition $definition, string $id, string $factoryCode);
/**
* Generates the code for the lazy proxy.
*
* @return string
*/
public function getProxyCode(Definition $definition);
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\LazyProxy;
/**
* @author Nicolas Grekas <p@tchwork.com>
*
* @internal
*/
class ProxyHelper
{
/**
* @return string|null The FQCN or builtin name of the type hint, or null when the type hint references an invalid self|parent context
*/
public static function getTypeHint(\ReflectionFunctionAbstract $r, \ReflectionParameter $p = null, bool $noBuiltin = false): ?string
{
if ($p instanceof \ReflectionParameter) {
$type = $p->getType();
} else {
$type = $r->getReturnType();
}
if (!$type) {
return null;
}
$types = [];
foreach ($type instanceof \ReflectionUnionType ? $type->getTypes() : [$type] as $type) {
$name = $type instanceof \ReflectionNamedType ? $type->getName() : (string) $type;
if ($type->isBuiltin()) {
if (!$noBuiltin) {
$types[] = $name;
}
continue;
}
$lcName = strtolower($name);
$prefix = $noBuiltin ? '' : '\\';
if ('self' !== $lcName && 'parent' !== $lcName) {
$types[] = '' !== $prefix ? $prefix.$name : $name;
continue;
}
if (!$r instanceof \ReflectionMethod) {
continue;
}
if ('self' === $lcName) {
$types[] = $prefix.$r->getDeclaringClass()->name;
} else {
$types[] = ($parent = $r->getDeclaringClass()->getParentClass()) ? $prefix.$parent->name : null;
}
}
return $types ? implode('|', $types) : null;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection;
use Symfony\Component\DependencyInjection\Argument\BoundArgument;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Exception\OutOfBoundsException;
/**
* Definition represents a service definition.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class Definition
{
private $class;
private $file;
private $factory;
private $shared = true;
private $deprecation = [];
private $properties = [];
private $calls = [];
private $instanceof = [];
private $autoconfigured = false;
private $configurator;
private $tags = [];
private $public = false;
private $synthetic = false;
private $abstract = false;
private $lazy = false;
private $decoratedService;
private $autowired = false;
private $changes = [];
private $bindings = [];
private $errors = [];
protected $arguments = [];
private static $defaultDeprecationTemplate = 'The "%service_id%" service is deprecated. You should stop using it, as it will be removed in the future.';
/**
* @internal
*
* Used to store the name of the inner id when using service decoration together with autowiring
*/
public $innerServiceId;
/**
* @internal
*
* Used to store the behavior to follow when using service decoration and the decorated service is invalid
*/
public $decorationOnInvalid;
public function __construct(string $class = null, array $arguments = [])
{
if (null !== $class) {
$this->setClass($class);
}
$this->arguments = $arguments;
}
/**
* Returns all changes tracked for the Definition object.
*
* @return array An array of changes for this Definition
*/
public function getChanges()
{
return $this->changes;
}
/**
* Sets the tracked changes for the Definition object.
*
* @param array $changes An array of changes for this Definition
*
* @return $this
*/
public function setChanges(array $changes)
{
$this->changes = $changes;
return $this;
}
/**
* Sets a factory.
*
* @param string|array|Reference $factory A PHP function, reference or an array containing a class/Reference and a method to call
*
* @return $this
*/
public function setFactory($factory)
{
$this->changes['factory'] = true;
if (\is_string($factory) && false !== strpos($factory, '::')) {
$factory = explode('::', $factory, 2);
} elseif ($factory instanceof Reference) {
$factory = [$factory, '__invoke'];
}
$this->factory = $factory;
return $this;
}
/**
* Gets the factory.
*
* @return string|array|null The PHP function or an array containing a class/Reference and a method to call
*/
public function getFactory()
{
return $this->factory;
}
/**
* Sets the service that this service is decorating.
*
* @param string|null $id The decorated service id, use null to remove decoration
* @param string|null $renamedId The new decorated service id
*
* @return $this
*
* @throws InvalidArgumentException in case the decorated service id and the new decorated service id are equals
*/
public function setDecoratedService(?string $id, ?string $renamedId = null, int $priority = 0, int $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE)
{
if ($renamedId && $id === $renamedId) {
throw new InvalidArgumentException(sprintf('The decorated service inner name for "%s" must be different than the service name itself.', $id));
}
$this->changes['decorated_service'] = true;
if (null === $id) {
$this->decoratedService = null;
} else {
$this->decoratedService = [$id, $renamedId, (int) $priority];
if (ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE !== $invalidBehavior) {
$this->decoratedService[] = $invalidBehavior;
}
}
return $this;
}
/**
* Gets the service that this service is decorating.
*
* @return array|null An array composed of the decorated service id, the new id for it and the priority of decoration, null if no service is decorated
*/
public function getDecoratedService()
{
return $this->decoratedService;
}
/**
* Sets the service class.
*
* @return $this
*/
public function setClass(?string $class)
{
$this->changes['class'] = true;
$this->class = $class;
return $this;
}
/**
* Gets the service class.
*
* @return string|null The service class
*/
public function getClass()
{
return $this->class;
}
/**
* Sets the arguments to pass to the service constructor/factory method.
*
* @return $this
*/
public function setArguments(array $arguments)
{
$this->arguments = $arguments;
return $this;
}
/**
* Sets the properties to define when creating the service.
*
* @return $this
*/
public function setProperties(array $properties)
{
$this->properties = $properties;
return $this;
}
/**
* Gets the properties to define when creating the service.
*
* @return array
*/
public function getProperties()
{
return $this->properties;
}
/**
* Sets a specific property.
*
* @param mixed $value
*
* @return $this
*/
public function setProperty(string $name, $value)
{
$this->properties[$name] = $value;
return $this;
}
/**
* Adds an argument to pass to the service constructor/factory method.
*
* @param mixed $argument An argument
*
* @return $this
*/
public function addArgument($argument)
{
$this->arguments[] = $argument;
return $this;
}
/**
* Replaces a specific argument.
*
* @param int|string $index
* @param mixed $argument
*
* @return $this
*
* @throws OutOfBoundsException When the replaced argument does not exist
*/
public function replaceArgument($index, $argument)
{
if (0 === \count($this->arguments)) {
throw new OutOfBoundsException('Cannot replace arguments if none have been configured yet.');
}
if (\is_int($index) && ($index < 0 || $index > \count($this->arguments) - 1)) {
throw new OutOfBoundsException(sprintf('The index "%d" is not in the range [0, %d].', $index, \count($this->arguments) - 1));
}
if (!\array_key_exists($index, $this->arguments)) {
throw new OutOfBoundsException(sprintf('The argument "%s" doesn\'t exist.', $index));
}
$this->arguments[$index] = $argument;
return $this;
}
/**
* Sets a specific argument.
*
* @param int|string $key
* @param mixed $value
*
* @return $this
*/
public function setArgument($key, $value)
{
$this->arguments[$key] = $value;
return $this;
}
/**
* Gets the arguments to pass to the service constructor/factory method.
*
* @return array The array of arguments
*/
public function getArguments()
{
return $this->arguments;
}
/**
* Gets an argument to pass to the service constructor/factory method.
*
* @param int|string $index
*
* @return mixed The argument value
*
* @throws OutOfBoundsException When the argument does not exist
*/
public function getArgument($index)
{
if (!\array_key_exists($index, $this->arguments)) {
throw new OutOfBoundsException(sprintf('The argument "%s" doesn\'t exist.', $index));
}
return $this->arguments[$index];
}
/**
* Sets the methods to call after service initialization.
*
* @return $this
*/
public function setMethodCalls(array $calls = [])
{
$this->calls = [];
foreach ($calls as $call) {
$this->addMethodCall($call[0], $call[1], $call[2] ?? false);
}
return $this;
}
/**
* Adds a method to call after service initialization.
*
* @param string $method The method name to call
* @param array $arguments An array of arguments to pass to the method call
* @param bool $returnsClone Whether the call returns the service instance or not
*
* @return $this
*
* @throws InvalidArgumentException on empty $method param
*/
public function addMethodCall(string $method, array $arguments = [], bool $returnsClone = false)
{
if (empty($method)) {
throw new InvalidArgumentException('Method name cannot be empty.');
}
$this->calls[] = $returnsClone ? [$method, $arguments, true] : [$method, $arguments];
return $this;
}
/**
* Removes a method to call after service initialization.
*
* @return $this
*/
public function removeMethodCall(string $method)
{
foreach ($this->calls as $i => $call) {
if ($call[0] === $method) {
unset($this->calls[$i]);
break;
}
}
return $this;
}
/**
* Check if the current definition has a given method to call after service initialization.
*
* @return bool
*/
public function hasMethodCall(string $method)
{
foreach ($this->calls as $call) {
if ($call[0] === $method) {
return true;
}
}
return false;
}
/**
* Gets the methods to call after service initialization.
*
* @return array An array of method calls
*/
public function getMethodCalls()
{
return $this->calls;
}
/**
* Sets the definition templates to conditionally apply on the current definition, keyed by parent interface/class.
*
* @param ChildDefinition[] $instanceof
*
* @return $this
*/
public function setInstanceofConditionals(array $instanceof)
{
$this->instanceof = $instanceof;
return $this;
}
/**
* Gets the definition templates to conditionally apply on the current definition, keyed by parent interface/class.
*
* @return ChildDefinition[]
*/
public function getInstanceofConditionals()
{
return $this->instanceof;
}
/**
* Sets whether or not instanceof conditionals should be prepended with a global set.
*
* @return $this
*/
public function setAutoconfigured(bool $autoconfigured)
{
$this->changes['autoconfigured'] = true;
$this->autoconfigured = $autoconfigured;
return $this;
}
/**
* @return bool
*/
public function isAutoconfigured()
{
return $this->autoconfigured;
}
/**
* Sets tags for this definition.
*
* @return $this
*/
public function setTags(array $tags)
{
$this->tags = $tags;
return $this;
}
/**
* Returns all tags.
*
* @return array An array of tags
*/
public function getTags()
{
return $this->tags;
}
/**
* Gets a tag by name.
*
* @return array An array of attributes
*/
public function getTag(string $name)
{
return isset($this->tags[$name]) ? $this->tags[$name] : [];
}
/**
* Adds a tag for this definition.
*
* @return $this
*/
public function addTag(string $name, array $attributes = [])
{
$this->tags[$name][] = $attributes;
return $this;
}
/**
* Whether this definition has a tag with the given name.
*
* @return bool
*/
public function hasTag(string $name)
{
return isset($this->tags[$name]);
}
/**
* Clears all tags for a given name.
*
* @return $this
*/
public function clearTag(string $name)
{
unset($this->tags[$name]);
return $this;
}
/**
* Clears the tags for this definition.
*
* @return $this
*/
public function clearTags()
{
$this->tags = [];
return $this;
}
/**
* Sets a file to require before creating the service.
*
* @return $this
*/
public function setFile(?string $file)
{
$this->changes['file'] = true;
$this->file = $file;
return $this;
}
/**
* Gets the file to require before creating the service.
*
* @return string|null The full pathname to include
*/
public function getFile()
{
return $this->file;
}
/**
* Sets if the service must be shared or not.
*
* @return $this
*/
public function setShared(bool $shared)
{
$this->changes['shared'] = true;
$this->shared = $shared;
return $this;
}
/**
* Whether this service is shared.
*
* @return bool
*/
public function isShared()
{
return $this->shared;
}
/**
* Sets the visibility of this service.
*
* @return $this
*/
public function setPublic(bool $boolean)
{
$this->changes['public'] = true;
$this->public = $boolean;
return $this;
}
/**
* Whether this service is public facing.
*
* @return bool
*/
public function isPublic()
{
return $this->public;
}
/**
* Sets if this service is private.
*
* @return $this
*
* @deprecated since Symfony 5.2, use setPublic() instead
*/
public function setPrivate(bool $boolean)
{
trigger_deprecation('symfony/dependency-injection', '5.2', 'The "%s()" method is deprecated, use "setPublic()" instead.', __METHOD__);
return $this->setPublic(!$boolean);
}
/**
* Whether this service is private.
*
* @return bool
*/
public function isPrivate()
{
return !$this->public;
}
/**
* Sets the lazy flag of this service.
*
* @return $this
*/
public function setLazy(bool $lazy)
{
$this->changes['lazy'] = true;
$this->lazy = $lazy;
return $this;
}
/**
* Whether this service is lazy.
*
* @return bool
*/
public function isLazy()
{
return $this->lazy;
}
/**
* Sets whether this definition is synthetic, that is not constructed by the
* container, but dynamically injected.
*
* @return $this
*/
public function setSynthetic(bool $boolean)
{
$this->synthetic = $boolean;
if (!isset($this->changes['public'])) {
$this->setPublic(true);
}
return $this;
}
/**
* Whether this definition is synthetic, that is not constructed by the
* container, but dynamically injected.
*
* @return bool
*/
public function isSynthetic()
{
return $this->synthetic;
}
/**
* Whether this definition is abstract, that means it merely serves as a
* template for other definitions.
*
* @return $this
*/
public function setAbstract(bool $boolean)
{
$this->abstract = $boolean;
return $this;
}
/**
* Whether this definition is abstract, that means it merely serves as a
* template for other definitions.
*
* @return bool
*/
public function isAbstract()
{
return $this->abstract;
}
/**
* Whether this definition is deprecated, that means it should not be called
* anymore.
*
* @param string $package The name of the composer package that is triggering the deprecation
* @param string $version The version of the package that introduced the deprecation
* @param string $message The deprecation message to use
*
* @return $this
*
* @throws InvalidArgumentException when the message template is invalid
*/
public function setDeprecated(/* string $package, string $version, string $message */)
{
$args = \func_get_args();
if (\func_num_args() < 3) {
trigger_deprecation('symfony/dependency-injection', '5.1', 'The signature of method "%s()" requires 3 arguments: "string $package, string $version, string $message", not defining them is deprecated.', __METHOD__);
$status = $args[0] ?? true;
if (!$status) {
trigger_deprecation('symfony/dependency-injection', '5.1', 'Passing a null message to un-deprecate a node is deprecated.');
}
$message = (string) ($args[1] ?? null);
$package = $version = '';
} else {
$status = true;
$package = (string) $args[0];
$version = (string) $args[1];
$message = (string) $args[2];
}
if ('' !== $message) {
if (preg_match('#[\r\n]|\*/#', $message)) {
throw new InvalidArgumentException('Invalid characters found in deprecation template.');
}
if (false === strpos($message, '%service_id%')) {
throw new InvalidArgumentException('The deprecation template must contain the "%service_id%" placeholder.');
}
}
$this->changes['deprecated'] = true;
$this->deprecation = $status ? ['package' => $package, 'version' => $version, 'message' => $message ?: self::$defaultDeprecationTemplate] : [];
return $this;
}
/**
* Whether this definition is deprecated, that means it should not be called
* anymore.
*
* @return bool
*/
public function isDeprecated()
{
return (bool) $this->deprecation;
}
/**
* Message to use if this definition is deprecated.
*
* @deprecated since Symfony 5.1, use "getDeprecation()" instead.
*
* @param string $id Service id relying on this definition
*
* @return string
*/
public function getDeprecationMessage(string $id)
{
trigger_deprecation('symfony/dependency-injection', '5.1', 'The "%s()" method is deprecated, use "getDeprecation()" instead.', __METHOD__);
return $this->getDeprecation($id)['message'];
}
/**
* @param string $id Service id relying on this definition
*/
public function getDeprecation(string $id): array
{
return [
'package' => $this->deprecation['package'],
'version' => $this->deprecation['version'],
'message' => str_replace('%service_id%', $id, $this->deprecation['message']),
];
}
/**
* Sets a configurator to call after the service is fully initialized.
*
* @param string|array|Reference $configurator A PHP function, reference or an array containing a class/Reference and a method to call
*
* @return $this
*/
public function setConfigurator($configurator)
{
$this->changes['configurator'] = true;
if (\is_string($configurator) && false !== strpos($configurator, '::')) {
$configurator = explode('::', $configurator, 2);
} elseif ($configurator instanceof Reference) {
$configurator = [$configurator, '__invoke'];
}
$this->configurator = $configurator;
return $this;
}
/**
* Gets the configurator to call after the service is fully initialized.
*
* @return callable|array|null
*/
public function getConfigurator()
{
return $this->configurator;
}
/**
* Is the definition autowired?
*
* @return bool
*/
public function isAutowired()
{
return $this->autowired;
}
/**
* Enables/disables autowiring.
*
* @return $this
*/
public function setAutowired(bool $autowired)
{
$this->changes['autowired'] = true;
$this->autowired = $autowired;
return $this;
}
/**
* Gets bindings.
*
* @return array|BoundArgument[]
*/
public function getBindings()
{
return $this->bindings;
}
/**
* Sets bindings.
*
* Bindings map $named or FQCN arguments to values that should be
* injected in the matching parameters (of the constructor, of methods
* called and of controller actions).
*
* @return $this
*/
public function setBindings(array $bindings)
{
foreach ($bindings as $key => $binding) {
if (0 < strpos($key, '$') && $key !== $k = preg_replace('/[ \t]*\$/', ' $', $key)) {
unset($bindings[$key]);
$bindings[$key = $k] = $binding;
}
if (!$binding instanceof BoundArgument) {
$bindings[$key] = new BoundArgument($binding);
}
}
$this->bindings = $bindings;
return $this;
}
/**
* Add an error that occurred when building this Definition.
*
* @param string|\Closure|self $error
*
* @return $this
*/
public function addError($error)
{
if ($error instanceof self) {
$this->errors = array_merge($this->errors, $error->errors);
} else {
$this->errors[] = $error;
}
return $this;
}
/**
* Returns any errors that occurred while building this Definition.
*
* @return array
*/
public function getErrors()
{
foreach ($this->errors as $i => $error) {
if ($error instanceof \Closure) {
$this->errors[$i] = (string) $error();
} elseif (!\is_string($error)) {
$this->errors[$i] = (string) $error;
}
}
return $this->errors;
}
public function hasErrors(): bool
{
return (bool) $this->errors;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection;
use Symfony\Component\ExpressionLanguage\ExpressionFunction;
use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface;
/**
* Define some ExpressionLanguage functions.
*
* To get a service, use service('request').
* To get a parameter, use parameter('kernel.debug').
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class ExpressionLanguageProvider implements ExpressionFunctionProviderInterface
{
private $serviceCompiler;
public function __construct(callable $serviceCompiler = null)
{
$this->serviceCompiler = $serviceCompiler;
}
public function getFunctions()
{
return [
new ExpressionFunction('service', $this->serviceCompiler ?: function ($arg) {
return sprintf('$this->get(%s)', $arg);
}, function (array $variables, $value) {
return $variables['container']->get($value);
}),
new ExpressionFunction('parameter', function ($arg) {
return sprintf('$this->getParameter(%s)', $arg);
}, function (array $variables, $value) {
return $variables['container']->getParameter($value);
}),
];
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection;
use Symfony\Component\DependencyInjection\Exception\EnvNotFoundException;
use Symfony\Component\DependencyInjection\Exception\ParameterCircularReferenceException;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
class EnvVarProcessor implements EnvVarProcessorInterface
{
private $container;
private $loaders;
private $loadedVars = [];
/**
* @param EnvVarLoaderInterface[] $loaders
*/
public function __construct(ContainerInterface $container, \Traversable $loaders = null)
{
$this->container = $container;
$this->loaders = $loaders ?? new \ArrayIterator();
}
/**
* {@inheritdoc}
*/
public static function getProvidedTypes()
{
return [
'base64' => 'string',
'bool' => 'bool',
'const' => 'bool|int|float|string|array',
'csv' => 'array',
'file' => 'string',
'float' => 'float',
'int' => 'int',
'json' => 'array',
'key' => 'bool|int|float|string|array',
'url' => 'array',
'query_string' => 'array',
'resolve' => 'string',
'default' => 'bool|int|float|string|array',
'string' => 'string',
'trim' => 'string',
'require' => 'bool|int|float|string|array',
];
}
/**
* {@inheritdoc}
*/
public function getEnv(string $prefix, string $name, \Closure $getEnv)
{
$i = strpos($name, ':');
if ('key' === $prefix) {
if (false === $i) {
throw new RuntimeException(sprintf('Invalid env "key:%s": a key specifier should be provided.', $name));
}
$next = substr($name, $i + 1);
$key = substr($name, 0, $i);
$array = $getEnv($next);
if (!\is_array($array)) {
throw new RuntimeException(sprintf('Resolved value of "%s" did not result in an array value.', $next));
}
if (!isset($array[$key]) && !\array_key_exists($key, $array)) {
throw new EnvNotFoundException(sprintf('Key "%s" not found in %s (resolved from "%s").', $key, json_encode($array), $next));
}
return $array[$key];
}
if ('default' === $prefix) {
if (false === $i) {
throw new RuntimeException(sprintf('Invalid env "default:%s": a fallback parameter should be provided.', $name));
}
$next = substr($name, $i + 1);
$default = substr($name, 0, $i);
if ('' !== $default && !$this->container->hasParameter($default)) {
throw new RuntimeException(sprintf('Invalid env fallback in "default:%s": parameter "%s" not found.', $name, $default));
}
try {
$env = $getEnv($next);
if ('' !== $env && null !== $env) {
return $env;
}
} catch (EnvNotFoundException $e) {
// no-op
}
return '' === $default ? null : $this->container->getParameter($default);
}
if ('file' === $prefix || 'require' === $prefix) {
if (!is_scalar($file = $getEnv($name))) {
throw new RuntimeException(sprintf('Invalid file name: env var "%s" is non-scalar.', $name));
}
if (!is_file($file)) {
throw new EnvNotFoundException(sprintf('File "%s" not found (resolved from "%s").', $file, $name));
}
if ('file' === $prefix) {
return file_get_contents($file);
} else {
return require $file;
}
}
if (false !== $i || 'string' !== $prefix) {
$env = $getEnv($name);
} elseif (isset($_ENV[$name])) {
$env = $_ENV[$name];
} elseif (isset($_SERVER[$name]) && 0 !== strpos($name, 'HTTP_')) {
$env = $_SERVER[$name];
} elseif (false === ($env = getenv($name)) || null === $env) { // null is a possible value because of thread safety issues
foreach ($this->loadedVars as $vars) {
if (false !== $env = ($vars[$name] ?? false)) {
break;
}
}
if (false === $env || null === $env) {
$loaders = $this->loaders;
$this->loaders = new \ArrayIterator();
try {
$i = 0;
$ended = true;
$count = $loaders instanceof \Countable ? $loaders->count() : 0;
foreach ($loaders as $loader) {
if (\count($this->loadedVars) > $i++) {
continue;
}
$this->loadedVars[] = $vars = $loader->loadEnvVars();
if (false !== $env = $vars[$name] ?? false) {
$ended = false;
break;
}
}
if ($ended || $count === $i) {
$loaders = $this->loaders;
}
} catch (ParameterCircularReferenceException $e) {
// skip loaders that need an env var that is not defined
} finally {
$this->loaders = $loaders;
}
}
if (false === $env || null === $env) {
if (!$this->container->hasParameter("env($name)")) {
throw new EnvNotFoundException(sprintf('Environment variable not found: "%s".', $name));
}
$env = $this->container->getParameter("env($name)");
}
}
if (null === $env) {
if (!isset($this->getProvidedTypes()[$prefix])) {
throw new RuntimeException(sprintf('Unsupported env var prefix "%s".', $prefix));
}
return null;
}
if (!is_scalar($env)) {
throw new RuntimeException(sprintf('Non-scalar env var "%s" cannot be cast to "%s".', $name, $prefix));
}
if ('string' === $prefix) {
return (string) $env;
}
if ('bool' === $prefix) {
return (bool) (filter_var($env, \FILTER_VALIDATE_BOOLEAN) ?: filter_var($env, \FILTER_VALIDATE_INT) ?: filter_var($env, \FILTER_VALIDATE_FLOAT));
}
if ('int' === $prefix) {
if (false === $env = filter_var($env, \FILTER_VALIDATE_INT) ?: filter_var($env, \FILTER_VALIDATE_FLOAT)) {
throw new RuntimeException(sprintf('Non-numeric env var "%s" cannot be cast to int.', $name));
}
return (int) $env;
}
if ('float' === $prefix) {
if (false === $env = filter_var($env, \FILTER_VALIDATE_FLOAT)) {
throw new RuntimeException(sprintf('Non-numeric env var "%s" cannot be cast to float.', $name));
}
return (float) $env;
}
if ('const' === $prefix) {
if (!\defined($env)) {
throw new RuntimeException(sprintf('Env var "%s" maps to undefined constant "%s".', $name, $env));
}
return \constant($env);
}
if ('base64' === $prefix) {
return base64_decode(strtr($env, '-_', '+/'));
}
if ('json' === $prefix) {
$env = json_decode($env, true);
if (\JSON_ERROR_NONE !== json_last_error()) {
throw new RuntimeException(sprintf('Invalid JSON in env var "%s": ', $name).json_last_error_msg());
}
if (null !== $env && !\is_array($env)) {
throw new RuntimeException(sprintf('Invalid JSON env var "%s": array or null expected, "%s" given.', $name, get_debug_type($env)));
}
return $env;
}
if ('url' === $prefix) {
$parsedEnv = parse_url($env);
if (false === $parsedEnv) {
throw new RuntimeException(sprintf('Invalid URL in env var "%s".', $name));
}
if (!isset($parsedEnv['scheme'], $parsedEnv['host'])) {
throw new RuntimeException(sprintf('Invalid URL env var "%s": schema and host expected, "%s" given.', $name, $env));
}
$parsedEnv += [
'port' => null,
'user' => null,
'pass' => null,
'path' => null,
'query' => null,
'fragment' => null,
];
// remove the '/' separator
$parsedEnv['path'] = '/' === $parsedEnv['path'] ? null : substr($parsedEnv['path'], 1);
return $parsedEnv;
}
if ('query_string' === $prefix) {
$queryString = parse_url($env, \PHP_URL_QUERY) ?: $env;
parse_str($queryString, $result);
return $result;
}
if ('resolve' === $prefix) {
return preg_replace_callback('/%%|%([^%\s]+)%/', function ($match) use ($name) {
if (!isset($match[1])) {
return '%';
}
$value = $this->container->getParameter($match[1]);
if (!is_scalar($value)) {
throw new RuntimeException(sprintf('Parameter "%s" found when resolving env var "%s" must be scalar, "%s" given.', $match[1], $name, get_debug_type($value)));
}
return $value;
}, $env);
}
if ('csv' === $prefix) {
return str_getcsv($env, ',', '"', \PHP_VERSION_ID >= 70400 ? '' : '\\');
}
if ('trim' === $prefix) {
return trim($env);
}
throw new RuntimeException(sprintf('Unsupported env var prefix "%s" for env name "%s".', $prefix, $name));
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection;
/**
* Represents a variable.
*
* $var = new Variable('a');
*
* will be dumped as
*
* $a
*
* by the PHP dumper.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class Variable
{
private $name;
public function __construct(string $name)
{
$this->name = $name;
}
/**
* @return string
*/
public function __toString()
{
return $this->name;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Dumper;
use Symfony\Component\DependencyInjection\ContainerBuilder;
/**
* Dumper is the abstract class for all built-in dumpers.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
abstract class Dumper implements DumperInterface
{
protected $container;
public function __construct(ContainerBuilder $container)
{
$this->container = $container;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Dumper;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
final class Preloader
{
public static function append(string $file, array $list): void
{
if (!file_exists($file)) {
throw new \LogicException(sprintf('File "%s" does not exist.', $file));
}
$cacheDir = \dirname($file);
$classes = [];
foreach ($list as $item) {
if (0 === strpos($item, $cacheDir)) {
file_put_contents($file, sprintf("require_once __DIR__.%s;\n", var_export(strtr(substr($item, \strlen($cacheDir)), \DIRECTORY_SEPARATOR, '/'), true)), \FILE_APPEND);
continue;
}
$classes[] = sprintf("\$classes[] = %s;\n", var_export($item, true));
}
file_put_contents($file, sprintf("\n\$classes = [];\n%sPreloader::preload(\$classes);\n", implode('', $classes)), \FILE_APPEND);
}
public static function preload(array $classes): void
{
set_error_handler(function ($t, $m, $f, $l) {
if (error_reporting() & $t) {
if (__FILE__ !== $f) {
throw new \ErrorException($m, 0, $t, $f, $l);
}
throw new \ReflectionException($m);
}
});
$prev = [];
$preloaded = [];
try {
while ($prev !== $classes) {
$prev = $classes;
foreach ($classes as $c) {
if (!isset($preloaded[$c])) {
self::doPreload($c, $preloaded);
}
}
$classes = array_merge(get_declared_classes(), get_declared_interfaces(), get_declared_traits());
}
} finally {
restore_error_handler();
}
}
private static function doPreload(string $class, array &$preloaded): void
{
if (isset($preloaded[$class]) || \in_array($class, ['self', 'static', 'parent'], true)) {
return;
}
$preloaded[$class] = true;
try {
$r = new \ReflectionClass($class);
if ($r->isInternal()) {
return;
}
$r->getConstants();
$r->getDefaultProperties();
if (\PHP_VERSION_ID >= 70400) {
foreach ($r->getProperties(\ReflectionProperty::IS_PUBLIC) as $p) {
self::preloadType($p->getType(), $preloaded);
}
}
foreach ($r->getMethods(\ReflectionMethod::IS_PUBLIC) as $m) {
foreach ($m->getParameters() as $p) {
if ($p->isDefaultValueAvailable() && $p->isDefaultValueConstant()) {
$c = $p->getDefaultValueConstantName();
if ($i = strpos($c, '::')) {
self::doPreload(substr($c, 0, $i), $preloaded);
}
}
self::preloadType($p->getType(), $preloaded);
}
self::preloadType($m->getReturnType(), $preloaded);
}
} catch (\Throwable $e) {
// ignore missing classes
}
}
private static function preloadType(?\ReflectionType $t, array &$preloaded): void
{
if (!$t) {
return;
}
foreach ($t instanceof \ReflectionUnionType ? $t->getTypes() : [$t] as $t) {
if (!$t->isBuiltin()) {
self::doPreload($t instanceof \ReflectionNamedType ? $t->getName() : $t, $preloaded);
}
}
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Dumper;
use Symfony\Component\DependencyInjection\Argument\ArgumentInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException;
use Symfony\Component\DependencyInjection\Parameter;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
use Symfony\Component\DependencyInjection\Reference;
/**
* GraphvizDumper dumps a service container as a graphviz file.
*
* You can convert the generated dot file with the dot utility (http://www.graphviz.org/):
*
* dot -Tpng container.dot > foo.png
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class GraphvizDumper extends Dumper
{
private $nodes;
private $edges;
// All values should be strings
private $options = [
'graph' => ['ratio' => 'compress'],
'node' => ['fontsize' => '11', 'fontname' => 'Arial', 'shape' => 'record'],
'edge' => ['fontsize' => '9', 'fontname' => 'Arial', 'color' => 'grey', 'arrowhead' => 'open', 'arrowsize' => '0.5'],
'node.instance' => ['fillcolor' => '#9999ff', 'style' => 'filled'],
'node.definition' => ['fillcolor' => '#eeeeee'],
'node.missing' => ['fillcolor' => '#ff9999', 'style' => 'filled'],
];
/**
* Dumps the service container as a graphviz graph.
*
* Available options:
*
* * graph: The default options for the whole graph
* * node: The default options for nodes
* * edge: The default options for edges
* * node.instance: The default options for services that are defined directly by object instances
* * node.definition: The default options for services that are defined via service definition instances
* * node.missing: The default options for missing services
*
* @return string The dot representation of the service container
*/
public function dump(array $options = [])
{
foreach (['graph', 'node', 'edge', 'node.instance', 'node.definition', 'node.missing'] as $key) {
if (isset($options[$key])) {
$this->options[$key] = array_merge($this->options[$key], $options[$key]);
}
}
$this->nodes = $this->findNodes();
$this->edges = [];
foreach ($this->container->getDefinitions() as $id => $definition) {
$this->edges[$id] = array_merge(
$this->findEdges($id, $definition->getArguments(), true, ''),
$this->findEdges($id, $definition->getProperties(), false, '')
);
foreach ($definition->getMethodCalls() as $call) {
$this->edges[$id] = array_merge(
$this->edges[$id],
$this->findEdges($id, $call[1], false, $call[0].'()')
);
}
}
return $this->container->resolveEnvPlaceholders($this->startDot().$this->addNodes().$this->addEdges().$this->endDot(), '__ENV_%s__');
}
private function addNodes(): string
{
$code = '';
foreach ($this->nodes as $id => $node) {
$aliases = $this->getAliases($id);
$code .= sprintf(" node_%s [label=\"%s\\n%s\\n\", shape=%s%s];\n", $this->dotize($id), $id.($aliases ? ' ('.implode(', ', $aliases).')' : ''), $node['class'], $this->options['node']['shape'], $this->addAttributes($node['attributes']));
}
return $code;
}
private function addEdges(): string
{
$code = '';
foreach ($this->edges as $id => $edges) {
foreach ($edges as $edge) {
$code .= sprintf(" node_%s -> node_%s [label=\"%s\" style=\"%s\"%s];\n", $this->dotize($id), $this->dotize($edge['to']), $edge['name'], $edge['required'] ? 'filled' : 'dashed', $edge['lazy'] ? ' color="#9999ff"' : '');
}
}
return $code;
}
/**
* Finds all edges belonging to a specific service id.
*/
private function findEdges(string $id, array $arguments, bool $required, string $name, bool $lazy = false): array
{
$edges = [];
foreach ($arguments as $argument) {
if ($argument instanceof Parameter) {
$argument = $this->container->hasParameter($argument) ? $this->container->getParameter($argument) : null;
} elseif (\is_string($argument) && preg_match('/^%([^%]+)%$/', $argument, $match)) {
$argument = $this->container->hasParameter($match[1]) ? $this->container->getParameter($match[1]) : null;
}
if ($argument instanceof Reference) {
$lazyEdge = $lazy;
if (!$this->container->has((string) $argument)) {
$this->nodes[(string) $argument] = ['name' => $name, 'required' => $required, 'class' => '', 'attributes' => $this->options['node.missing']];
} elseif ('service_container' !== (string) $argument) {
$lazyEdge = $lazy || $this->container->getDefinition((string) $argument)->isLazy();
}
$edges[] = ['name' => $name, 'required' => $required, 'to' => $argument, 'lazy' => $lazyEdge];
} elseif ($argument instanceof ArgumentInterface) {
$edges = array_merge($edges, $this->findEdges($id, $argument->getValues(), $required, $name, true));
} elseif ($argument instanceof Definition) {
$edges = array_merge($edges,
$this->findEdges($id, $argument->getArguments(), $required, ''),
$this->findEdges($id, $argument->getProperties(), false, '')
);
foreach ($argument->getMethodCalls() as $call) {
$edges = array_merge($edges, $this->findEdges($id, $call[1], false, $call[0].'()'));
}
} elseif (\is_array($argument)) {
$edges = array_merge($edges, $this->findEdges($id, $argument, $required, $name, $lazy));
}
}
return $edges;
}
private function findNodes(): array
{
$nodes = [];
$container = $this->cloneContainer();
foreach ($container->getDefinitions() as $id => $definition) {
$class = $definition->getClass();
if ('\\' === substr($class, 0, 1)) {
$class = substr($class, 1);
}
try {
$class = $this->container->getParameterBag()->resolveValue($class);
} catch (ParameterNotFoundException $e) {
}
$nodes[$id] = ['class' => str_replace('\\', '\\\\', $class), 'attributes' => array_merge($this->options['node.definition'], ['style' => $definition->isShared() ? 'filled' : 'dotted'])];
$container->setDefinition($id, new Definition('stdClass'));
}
foreach ($container->getServiceIds() as $id) {
if (\array_key_exists($id, $container->getAliases())) {
continue;
}
if (!$container->hasDefinition($id)) {
$nodes[$id] = ['class' => str_replace('\\', '\\\\', \get_class($container->get($id))), 'attributes' => $this->options['node.instance']];
}
}
return $nodes;
}
private function cloneContainer(): ContainerBuilder
{
$parameterBag = new ParameterBag($this->container->getParameterBag()->all());
$container = new ContainerBuilder($parameterBag);
$container->setDefinitions($this->container->getDefinitions());
$container->setAliases($this->container->getAliases());
$container->setResources($this->container->getResources());
foreach ($this->container->getExtensions() as $extension) {
$container->registerExtension($extension);
}
return $container;
}
private function startDot(): string
{
return sprintf("digraph sc {\n %s\n node [%s];\n edge [%s];\n\n",
$this->addOptions($this->options['graph']),
$this->addOptions($this->options['node']),
$this->addOptions($this->options['edge'])
);
}
private function endDot(): string
{
return "}\n";
}
private function addAttributes(array $attributes): string
{
$code = [];
foreach ($attributes as $k => $v) {
$code[] = sprintf('%s="%s"', $k, $v);
}
return $code ? ', '.implode(', ', $code) : '';
}
private function addOptions(array $options): string
{
$code = [];
foreach ($options as $k => $v) {
$code[] = sprintf('%s="%s"', $k, $v);
}
return implode(' ', $code);
}
private function dotize(string $id): string
{
return preg_replace('/\W/i', '_', $id);
}
private function getAliases(string $id): array
{
$aliases = [];
foreach ($this->container->getAliases() as $alias => $origin) {
if ($id == $origin) {
$aliases[] = $alias;
}
}
return $aliases;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Dumper;
use Symfony\Component\DependencyInjection\Alias;
use Symfony\Component\DependencyInjection\Argument\AbstractArgument;
use Symfony\Component\DependencyInjection\Argument\ArgumentInterface;
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\LogicException;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
use Symfony\Component\DependencyInjection\Parameter;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\ExpressionLanguage\Expression;
use Symfony\Component\Yaml\Dumper as YmlDumper;
use Symfony\Component\Yaml\Parser;
use Symfony\Component\Yaml\Tag\TaggedValue;
use Symfony\Component\Yaml\Yaml;
/**
* YamlDumper dumps a service container as a YAML string.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class YamlDumper extends Dumper
{
private $dumper;
/**
* Dumps the service container as an YAML string.
*
* @return string A YAML string representing of the service container
*/
public function dump(array $options = [])
{
if (!class_exists('Symfony\Component\Yaml\Dumper')) {
throw new LogicException('Unable to dump the container as the Symfony Yaml Component is not installed.');
}
if (null === $this->dumper) {
$this->dumper = new YmlDumper();
}
return $this->container->resolveEnvPlaceholders($this->addParameters()."\n".$this->addServices());
}
private function addService(string $id, Definition $definition): string
{
$code = " $id:\n";
if ($class = $definition->getClass()) {
if ('\\' === substr($class, 0, 1)) {
$class = substr($class, 1);
}
$code .= sprintf(" class: %s\n", $this->dumper->dump($class));
}
if (!$definition->isPrivate()) {
$code .= sprintf(" public: %s\n", $definition->isPublic() ? 'true' : 'false');
}
$tagsCode = '';
foreach ($definition->getTags() as $name => $tags) {
foreach ($tags as $attributes) {
$att = [];
foreach ($attributes as $key => $value) {
$att[] = sprintf('%s: %s', $this->dumper->dump($key), $this->dumper->dump($value));
}
$att = $att ? ': { '.implode(', ', $att).' }' : '';
$tagsCode .= sprintf(" - %s%s\n", $this->dumper->dump($name), $att);
}
}
if ($tagsCode) {
$code .= " tags:\n".$tagsCode;
}
if ($definition->getFile()) {
$code .= sprintf(" file: %s\n", $this->dumper->dump($definition->getFile()));
}
if ($definition->isSynthetic()) {
$code .= " synthetic: true\n";
}
if ($definition->isDeprecated()) {
$code .= " deprecated:\n";
foreach ($definition->getDeprecation('%service_id%') as $key => $value) {
if ('' !== $value) {
$code .= sprintf(" %s: %s\n", $key, $this->dumper->dump($value));
}
}
}
if ($definition->isAutowired()) {
$code .= " autowire: true\n";
}
if ($definition->isAutoconfigured()) {
$code .= " autoconfigure: true\n";
}
if ($definition->isAbstract()) {
$code .= " abstract: true\n";
}
if ($definition->isLazy()) {
$code .= " lazy: true\n";
}
if ($definition->getArguments()) {
$code .= sprintf(" arguments: %s\n", $this->dumper->dump($this->dumpValue($definition->getArguments()), 0));
}
if ($definition->getProperties()) {
$code .= sprintf(" properties: %s\n", $this->dumper->dump($this->dumpValue($definition->getProperties()), 0));
}
if ($definition->getMethodCalls()) {
$code .= sprintf(" calls:\n%s\n", $this->dumper->dump($this->dumpValue($definition->getMethodCalls()), 1, 12));
}
if (!$definition->isShared()) {
$code .= " shared: false\n";
}
if (null !== $decoratedService = $definition->getDecoratedService()) {
[$decorated, $renamedId, $priority] = $decoratedService;
$code .= sprintf(" decorates: %s\n", $decorated);
if (null !== $renamedId) {
$code .= sprintf(" decoration_inner_name: %s\n", $renamedId);
}
if (0 !== $priority) {
$code .= sprintf(" decoration_priority: %s\n", $priority);
}
$decorationOnInvalid = $decoratedService[3] ?? ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE;
if (\in_array($decorationOnInvalid, [ContainerInterface::IGNORE_ON_INVALID_REFERENCE, ContainerInterface::NULL_ON_INVALID_REFERENCE])) {
$invalidBehavior = ContainerInterface::NULL_ON_INVALID_REFERENCE === $decorationOnInvalid ? 'null' : 'ignore';
$code .= sprintf(" decoration_on_invalid: %s\n", $invalidBehavior);
}
}
if ($callable = $definition->getFactory()) {
$code .= sprintf(" factory: %s\n", $this->dumper->dump($this->dumpCallable($callable), 0));
}
if ($callable = $definition->getConfigurator()) {
$code .= sprintf(" configurator: %s\n", $this->dumper->dump($this->dumpCallable($callable), 0));
}
return $code;
}
private function addServiceAlias(string $alias, Alias $id): string
{
$deprecated = '';
if ($id->isDeprecated()) {
$deprecated = " deprecated:\n";
foreach ($id->getDeprecation('%alias_id%') as $key => $value) {
if ('' !== $value) {
$deprecated .= sprintf(" %s: %s\n", $key, $value);
}
}
}
if (!$id->isDeprecated() && $id->isPrivate()) {
return sprintf(" %s: '@%s'\n", $alias, $id);
}
if ($id->isPublic()) {
$deprecated = " public: true\n".$deprecated;
}
return sprintf(" %s:\n alias: %s\n%s", $alias, $id, $deprecated);
}
private function addServices(): string
{
if (!$this->container->getDefinitions()) {
return '';
}
$code = "services:\n";
foreach ($this->container->getDefinitions() as $id => $definition) {
$code .= $this->addService($id, $definition);
}
$aliases = $this->container->getAliases();
foreach ($aliases as $alias => $id) {
while (isset($aliases[(string) $id])) {
$id = $aliases[(string) $id];
}
$code .= $this->addServiceAlias($alias, $id);
}
return $code;
}
private function addParameters(): string
{
if (!$this->container->getParameterBag()->all()) {
return '';
}
$parameters = $this->prepareParameters($this->container->getParameterBag()->all(), $this->container->isCompiled());
return $this->dumper->dump(['parameters' => $parameters], 2);
}
/**
* Dumps callable to YAML format.
*
* @param mixed $callable
*
* @return mixed
*/
private function dumpCallable($callable)
{
if (\is_array($callable)) {
if ($callable[0] instanceof Reference) {
$callable = [$this->getServiceCall((string) $callable[0], $callable[0]), $callable[1]];
} else {
$callable = [$callable[0], $callable[1]];
}
}
return $callable;
}
/**
* Dumps the value to YAML format.
*
* @return mixed
*
* @throws RuntimeException When trying to dump object or resource
*/
private function dumpValue($value)
{
if ($value instanceof ServiceClosureArgument) {
$value = $value->getValues()[0];
}
if ($value instanceof ArgumentInterface) {
$tag = $value;
if ($value instanceof TaggedIteratorArgument || ($value instanceof ServiceLocatorArgument && $tag = $value->getTaggedIteratorArgument())) {
if (null === $tag->getIndexAttribute()) {
$content = $tag->getTag();
} else {
$content = [
'tag' => $tag->getTag(),
'index_by' => $tag->getIndexAttribute(),
];
if (null !== $tag->getDefaultIndexMethod()) {
$content['default_index_method'] = $tag->getDefaultIndexMethod();
}
if (null !== $tag->getDefaultPriorityMethod()) {
$content['default_priority_method'] = $tag->getDefaultPriorityMethod();
}
}
return new TaggedValue($value instanceof TaggedIteratorArgument ? 'tagged_iterator' : 'tagged_locator', $content);
}
if ($value instanceof IteratorArgument) {
$tag = 'iterator';
} elseif ($value instanceof ServiceLocatorArgument) {
$tag = 'service_locator';
} else {
throw new RuntimeException(sprintf('Unspecified Yaml tag for type "%s".', get_debug_type($value)));
}
return new TaggedValue($tag, $this->dumpValue($value->getValues()));
}
if (\is_array($value)) {
$code = [];
foreach ($value as $k => $v) {
$code[$k] = $this->dumpValue($v);
}
return $code;
} elseif ($value instanceof Reference) {
return $this->getServiceCall((string) $value, $value);
} elseif ($value instanceof Parameter) {
return $this->getParameterCall((string) $value);
} elseif ($value instanceof Expression) {
return $this->getExpressionCall((string) $value);
} elseif ($value instanceof Definition) {
return new TaggedValue('service', (new Parser())->parse("_:\n".$this->addService('_', $value), Yaml::PARSE_CUSTOM_TAGS)['_']['_']);
} elseif ($value instanceof AbstractArgument) {
return new TaggedValue('abstract', $value->getText());
} elseif (\is_object($value) || \is_resource($value)) {
throw new RuntimeException('Unable to dump a service container if a parameter is an object or a resource.');
}
return $value;
}
private function getServiceCall(string $id, Reference $reference = null): string
{
if (null !== $reference) {
switch ($reference->getInvalidBehavior()) {
case ContainerInterface::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE: break;
case ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE: break;
case ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE: return sprintf('@!%s', $id);
default: return sprintf('@?%s', $id);
}
}
return sprintf('@%s', $id);
}
private function getParameterCall(string $id): string
{
return sprintf('%%%s%%', $id);
}
private function getExpressionCall(string $expression): string
{
return sprintf('@=%s', $expression);
}
private function prepareParameters(array $parameters, bool $escape = true): array
{
$filtered = [];
foreach ($parameters as $key => $value) {
if (\is_array($value)) {
$value = $this->prepareParameters($value, $escape);
} elseif ($value instanceof Reference || \is_string($value) && 0 === strpos($value, '@')) {
$value = '@'.$value;
}
$filtered[$key] = $value;
}
return $escape ? $this->escape($filtered) : $filtered;
}
private function escape(array $arguments): array
{
$args = [];
foreach ($arguments as $k => $v) {
if (\is_array($v)) {
$args[$k] = $this->escape($v);
} elseif (\is_string($v)) {
$args[$k] = str_replace('%', '%%', $v);
} else {
$args[$k] = $v;
}
}
return $args;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Dumper;
/**
* DumperInterface is the interface implemented by service container dumper classes.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
interface DumperInterface
{
/**
* Dumps the service container.
*
* @return string|array The representation of the service container
*/
public function dump(array $options = []);
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Dumper;
use Composer\Autoload\ClassLoader;
use Symfony\Component\Debug\DebugClassLoader as LegacyDebugClassLoader;
use Symfony\Component\DependencyInjection\Argument\AbstractArgument;
use Symfony\Component\DependencyInjection\Argument\ArgumentInterface;
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
use Symfony\Component\DependencyInjection\Argument\ServiceLocator;
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
use Symfony\Component\DependencyInjection\Compiler\AnalyzeServiceReferencesPass;
use Symfony\Component\DependencyInjection\Compiler\CheckCircularReferencesPass;
use Symfony\Component\DependencyInjection\Compiler\ServiceReferenceGraphEdge;
use Symfony\Component\DependencyInjection\Compiler\ServiceReferenceGraphNode;
use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\EnvParameterException;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Exception\LogicException;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException;
use Symfony\Component\DependencyInjection\ExpressionLanguage;
use Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\DumperInterface as ProxyDumper;
use Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\NullDumper;
use Symfony\Component\DependencyInjection\Loader\FileLoader;
use Symfony\Component\DependencyInjection\Parameter;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\ServiceLocator as BaseServiceLocator;
use Symfony\Component\DependencyInjection\TypedReference;
use Symfony\Component\DependencyInjection\Variable;
use Symfony\Component\ErrorHandler\DebugClassLoader;
use Symfony\Component\ExpressionLanguage\Expression;
use Symfony\Component\HttpKernel\Kernel;
/**
* PhpDumper dumps a service container as a PHP class.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class PhpDumper extends Dumper
{
/**
* Characters that might appear in the generated variable name as first character.
*/
const FIRST_CHARS = 'abcdefghijklmnopqrstuvwxyz';
/**
* Characters that might appear in the generated variable name as any but the first character.
*/
const NON_FIRST_CHARS = 'abcdefghijklmnopqrstuvwxyz0123456789_';
private $definitionVariables;
private $referenceVariables;
private $variableCount;
private $inlinedDefinitions;
private $serviceCalls;
private $reservedVariables = ['instance', 'class', 'this', 'container'];
private $expressionLanguage;
private $targetDirRegex;
private $targetDirMaxMatches;
private $docStar;
private $serviceIdToMethodNameMap;
private $usedMethodNames;
private $namespace;
private $asFiles;
private $hotPathTag;
private $preloadTags;
private $inlineFactories;
private $inlineRequires;
private $inlinedRequires = [];
private $circularReferences = [];
private $singleUsePrivateIds = [];
private $preload = [];
private $addThrow = false;
private $addGetService = false;
private $locatedIds = [];
private $serviceLocatorTag;
private $exportedVariables = [];
private $baseClass;
/**
* @var ProxyDumper
*/
private $proxyDumper;
/**
* {@inheritdoc}
*/
public function __construct(ContainerBuilder $container)
{
if (!$container->isCompiled()) {
throw new LogicException('Cannot dump an uncompiled container.');
}
parent::__construct($container);
}
/**
* Sets the dumper to be used when dumping proxies in the generated container.
*/
public function setProxyDumper(ProxyDumper $proxyDumper)
{
$this->proxyDumper = $proxyDumper;
}
/**
* Dumps the service container as a PHP class.
*
* Available options:
*
* * class: The class name
* * base_class: The base class name
* * namespace: The class namespace
* * as_files: To split the container in several files
*
* @return string|array A PHP class representing the service container or an array of PHP files if the "as_files" option is set
*
* @throws EnvParameterException When an env var exists but has not been dumped
*/
public function dump(array $options = [])
{
$this->locatedIds = [];
$this->targetDirRegex = null;
$this->inlinedRequires = [];
$this->exportedVariables = [];
$options = array_merge([
'class' => 'ProjectServiceContainer',
'base_class' => 'Container',
'namespace' => '',
'as_files' => false,
'debug' => true,
'hot_path_tag' => 'container.hot_path',
'preload_tags' => ['container.preload', 'container.no_preload'],
'inline_factories_parameter' => 'container.dumper.inline_factories',
'inline_class_loader_parameter' => 'container.dumper.inline_class_loader',
'preload_classes' => [],
'service_locator_tag' => 'container.service_locator',
'build_time' => time(),
], $options);
$this->addThrow = $this->addGetService = false;
$this->namespace = $options['namespace'];
$this->asFiles = $options['as_files'];
$this->hotPathTag = $options['hot_path_tag'];
$this->preloadTags = $options['preload_tags'];
$this->inlineFactories = $this->asFiles && $options['inline_factories_parameter'] && $this->container->hasParameter($options['inline_factories_parameter']) && $this->container->getParameter($options['inline_factories_parameter']);
$this->inlineRequires = $options['inline_class_loader_parameter'] && ($this->container->hasParameter($options['inline_class_loader_parameter']) ? $this->container->getParameter($options['inline_class_loader_parameter']) : (\PHP_VERSION_ID < 70400 || $options['debug']));
$this->serviceLocatorTag = $options['service_locator_tag'];
if (0 !== strpos($baseClass = $options['base_class'], '\\') && 'Container' !== $baseClass) {
$baseClass = sprintf('%s\%s', $options['namespace'] ? '\\'.$options['namespace'] : '', $baseClass);
$this->baseClass = $baseClass;
} elseif ('Container' === $baseClass) {
$this->baseClass = Container::class;
} else {
$this->baseClass = $baseClass;
}
$this->initializeMethodNamesMap('Container' === $baseClass ? Container::class : $baseClass);
if ($this->getProxyDumper() instanceof NullDumper) {
(new AnalyzeServiceReferencesPass(true, false))->process($this->container);
try {
(new CheckCircularReferencesPass())->process($this->container);
} catch (ServiceCircularReferenceException $e) {
$path = $e->getPath();
end($path);
$path[key($path)] .= '". Try running "composer require symfony/proxy-manager-bridge';
throw new ServiceCircularReferenceException($e->getServiceId(), $path);
}
}
$this->analyzeReferences();
$this->docStar = $options['debug'] ? '*' : '';
if (!empty($options['file']) && is_dir($dir = \dirname($options['file']))) {
// Build a regexp where the first root dirs are mandatory,
// but every other sub-dir is optional up to the full path in $dir
// Mandate at least 1 root dir and not more than 5 optional dirs.
$dir = explode(\DIRECTORY_SEPARATOR, realpath($dir));
$i = \count($dir);
if (2 + (int) ('\\' === \DIRECTORY_SEPARATOR) <= $i) {
$regex = '';
$lastOptionalDir = $i > 8 ? $i - 5 : (2 + (int) ('\\' === \DIRECTORY_SEPARATOR));
$this->targetDirMaxMatches = $i - $lastOptionalDir;
while (--$i >= $lastOptionalDir) {
$regex = sprintf('(%s%s)?', preg_quote(\DIRECTORY_SEPARATOR.$dir[$i], '#'), $regex);
}
do {
$regex = preg_quote(\DIRECTORY_SEPARATOR.$dir[$i], '#').$regex;
} while (0 < --$i);
$this->targetDirRegex = '#(^|file://|[:;, \|\r\n])'.preg_quote($dir[0], '#').$regex.'#';
}
}
$proxyClasses = $this->inlineFactories ? $this->generateProxyClasses() : null;
if ($options['preload_classes']) {
$this->preload = array_combine($options['preload_classes'], $options['preload_classes']);
}
$code =
$this->startClass($options['class'], $baseClass).
$this->addServices($services).
$this->addDeprecatedAliases().
$this->addDefaultParametersMethod()
;
$proxyClasses = $proxyClasses ?? $this->generateProxyClasses();
if ($this->addGetService) {
$code = preg_replace(
"/(\r?\n\r?\n public function __construct.+?\\{\r?\n)/s",
"\n protected \$getService;$1 \$this->getService = \\Closure::fromCallable([\$this, 'getService']);\n",
$code,
1
);
}
if ($this->asFiles) {
$fileTemplate = <<<EOF
<?php
use Symfony\Component\DependencyInjection\Argument\RewindableGenerator;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
/*{$this->docStar}
* @internal This class has been auto-generated by the Symfony Dependency Injection Component.
*/
class %s extends {$options['class']}
{%s}
EOF;
$files = [];
$preloadedFiles = [];
$ids = $this->container->getRemovedIds();
foreach ($this->container->getDefinitions() as $id => $definition) {
if (!$definition->isPublic()) {
$ids[$id] = true;
}
}
if ($ids = array_keys($ids)) {
sort($ids);
$c = "<?php\n\nreturn [\n";
foreach ($ids as $id) {
$c .= ' '.$this->doExport($id)." => true,\n";
}
$files['removed-ids.php'] = $c."];\n";
}
if (!$this->inlineFactories) {
foreach ($this->generateServiceFiles($services) as $file => [$c, $preload]) {
$files[$file] = sprintf($fileTemplate, substr($file, 0, -4), $c);
if ($preload) {
$preloadedFiles[$file] = $file;
}
}
foreach ($proxyClasses as $file => $c) {
$files[$file] = "<?php\n".$c;
$preloadedFiles[$file] = $file;
}
}
$code .= $this->endClass();
if ($this->inlineFactories) {
foreach ($proxyClasses as $c) {
$code .= $c;
}
}
$files[$options['class'].'.php'] = $code;
$preloadedFiles[$options['class'].'.php'] = $options['class'].'.php';
$hash = ucfirst(strtr(ContainerBuilder::hash($files), '._', 'xx'));
$code = [];
foreach ($files as $file => $c) {
$code["Container{$hash}/{$file}"] = substr_replace($c, "<?php\n\nnamespace Container{$hash};\n", 0, 6);
if (isset($preloadedFiles[$file])) {
$preloadedFiles[$file] = "Container{$hash}/{$file}";
}
}
$namespaceLine = $this->namespace ? "\nnamespace {$this->namespace};\n" : '';
$time = $options['build_time'];
$id = hash('crc32', $hash.$time);
$this->asFiles = false;
if ($this->preload && null !== $autoloadFile = $this->getAutoloadFile()) {
$autoloadFile = trim($this->export($autoloadFile), '()\\');
$preloadedFiles = array_reverse($preloadedFiles);
$preloadedFiles = implode("';\nrequire __DIR__.'/", $preloadedFiles);
$code[$options['class'].'.preload.php'] = <<<EOF
<?php
// This file has been auto-generated by the Symfony Dependency Injection Component
// You can reference it in the "opcache.preload" php.ini setting on PHP >= 7.4 when preloading is desired
use Symfony\Component\DependencyInjection\Dumper\Preloader;
if (in_array(PHP_SAPI, ['cli', 'phpdbg'], true)) {
return;
}
require $autoloadFile;
require __DIR__.'/$preloadedFiles';
\$classes = [];
EOF;
foreach ($this->preload as $class) {
if (!$class || false !== strpos($class, '$') || \in_array($class, ['int', 'float', 'string', 'bool', 'resource', 'object', 'array', 'null', 'callable', 'iterable', 'mixed', 'void'], true)) {
continue;
}
if (!(class_exists($class, false) || interface_exists($class, false) || trait_exists($class, false)) || (new \ReflectionClass($class))->isUserDefined()) {
$code[$options['class'].'.preload.php'] .= sprintf("\$classes[] = '%s';\n", $class);
}
}
$code[$options['class'].'.preload.php'] .= <<<'EOF'
Preloader::preload($classes);
EOF;
}
$code[$options['class'].'.php'] = <<<EOF
<?php
{$namespaceLine}
// This file has been auto-generated by the Symfony Dependency Injection Component for internal use.
if (\\class_exists(\\Container{$hash}\\{$options['class']}::class, false)) {
// no-op
} elseif (!include __DIR__.'/Container{$hash}/{$options['class']}.php') {
touch(__DIR__.'/Container{$hash}.legacy');
return;
}
if (!\\class_exists({$options['class']}::class, false)) {
\\class_alias(\\Container{$hash}\\{$options['class']}::class, {$options['class']}::class, false);
}
return new \\Container{$hash}\\{$options['class']}([
'container.build_hash' => '$hash',
'container.build_id' => '$id',
'container.build_time' => $time,
], __DIR__.\\DIRECTORY_SEPARATOR.'Container{$hash}');
EOF;
} else {
$code .= $this->endClass();
foreach ($proxyClasses as $c) {
$code .= $c;
}
}
$this->targetDirRegex = null;
$this->inlinedRequires = [];
$this->circularReferences = [];
$this->locatedIds = [];
$this->exportedVariables = [];
$this->preload = [];
$unusedEnvs = [];
foreach ($this->container->getEnvCounters() as $env => $use) {
if (!$use) {
$unusedEnvs[] = $env;
}
}
if ($unusedEnvs) {
throw new EnvParameterException($unusedEnvs, null, 'Environment variables "%s" are never used. Please, check your container\'s configuration.');
}
return $code;
}
/**
* Retrieves the currently set proxy dumper or instantiates one.
*/
private function getProxyDumper(): ProxyDumper
{
if (!$this->proxyDumper) {
$this->proxyDumper = new NullDumper();
}
return $this->proxyDumper;
}
private function analyzeReferences()
{
(new AnalyzeServiceReferencesPass(false, !$this->getProxyDumper() instanceof NullDumper))->process($this->container);
$checkedNodes = [];
$this->circularReferences = [];
$this->singleUsePrivateIds = [];
foreach ($this->container->getCompiler()->getServiceReferenceGraph()->getNodes() as $id => $node) {
if (!$node->getValue() instanceof Definition) {
continue;
}
if ($this->isSingleUsePrivateNode($node)) {
$this->singleUsePrivateIds[$id] = $id;
}
$this->collectCircularReferences($id, $node->getOutEdges(), $checkedNodes);
}
$this->container->getCompiler()->getServiceReferenceGraph()->clear();
$this->singleUsePrivateIds = array_diff_key($this->singleUsePrivateIds, $this->circularReferences);
}
private function collectCircularReferences(string $sourceId, array $edges, array &$checkedNodes, array &$loops = [], array $path = [], bool $byConstructor = true): void
{
$path[$sourceId] = $byConstructor;
$checkedNodes[$sourceId] = true;
foreach ($edges as $edge) {
$node = $edge->getDestNode();
$id = $node->getId();
if ($sourceId === $id || !$node->getValue() instanceof Definition || $edge->isLazy() || $edge->isWeak()) {
continue;
}
if (isset($path[$id])) {
$loop = null;
$loopByConstructor = $edge->isReferencedByConstructor();
$pathInLoop = [$id, []];
foreach ($path as $k => $pathByConstructor) {
if (null !== $loop) {
$loop[] = $k;
$pathInLoop[1][$k] = $pathByConstructor;
$loops[$k][] = &$pathInLoop;
$loopByConstructor = $loopByConstructor && $pathByConstructor;
} elseif ($k === $id) {
$loop = [];
}
}
$this->addCircularReferences($id, $loop, $loopByConstructor);
} elseif (!isset($checkedNodes[$id])) {
$this->collectCircularReferences($id, $node->getOutEdges(), $checkedNodes, $loops, $path, $edge->isReferencedByConstructor());
} elseif (isset($loops[$id])) {
// we already had detected loops for this edge
// let's check if we have a common ancestor in one of the detected loops
foreach ($loops[$id] as [$first, $loopPath]) {
if (!isset($path[$first])) {
continue;
}
// We have a common ancestor, let's fill the current path
$fillPath = null;
foreach ($loopPath as $k => $pathByConstructor) {
if (null !== $fillPath) {
$fillPath[$k] = $pathByConstructor;
} elseif ($k === $id) {
$fillPath = $path;
$fillPath[$k] = $pathByConstructor;
}
}
// we can now build the loop
$loop = null;
$loopByConstructor = $edge->isReferencedByConstructor();
foreach ($fillPath as $k => $pathByConstructor) {
if (null !== $loop) {
$loop[] = $k;
$loopByConstructor = $loopByConstructor && $pathByConstructor;
} elseif ($k === $first) {
$loop = [];
}
}
$this->addCircularReferences($first, $loop, true);
break;
}
}
}
unset($path[$sourceId]);
}
private function addCircularReferences(string $sourceId, array $currentPath, bool $byConstructor)
{
$currentId = $sourceId;
$currentPath = array_reverse($currentPath);
$currentPath[] = $currentId;
foreach ($currentPath as $parentId) {
if (empty($this->circularReferences[$parentId][$currentId])) {
$this->circularReferences[$parentId][$currentId] = $byConstructor;
}
$currentId = $parentId;
}
}
private function collectLineage(string $class, array &$lineage)
{
if (isset($lineage[$class])) {
return;
}
if (!$r = $this->container->getReflectionClass($class, false)) {
return;
}
if (is_a($class, $this->baseClass, true)) {
return;
}
$file = $r->getFileName();
if (') : eval()\'d code' === substr($file, -17)) {
$file = substr($file, 0, strrpos($file, '(', -17));
}
if (!$file || $this->doExport($file) === $exportedFile = $this->export($file)) {
return;
}
$lineage[$class] = substr($exportedFile, 1, -1);
if ($parent = $r->getParentClass()) {
$this->collectLineage($parent->name, $lineage);
}
foreach ($r->getInterfaces() as $parent) {
$this->collectLineage($parent->name, $lineage);
}
foreach ($r->getTraits() as $parent) {
$this->collectLineage($parent->name, $lineage);
}
unset($lineage[$class]);
$lineage[$class] = substr($exportedFile, 1, -1);
}
private function generateProxyClasses(): array
{
$proxyClasses = [];
$alreadyGenerated = [];
$definitions = $this->container->getDefinitions();
$strip = '' === $this->docStar && method_exists('Symfony\Component\HttpKernel\Kernel', 'stripComments');
$proxyDumper = $this->getProxyDumper();
ksort($definitions);
foreach ($definitions as $definition) {
if (!$proxyDumper->isProxyCandidate($definition)) {
continue;
}
if (isset($alreadyGenerated[$class = $definition->getClass()])) {
continue;
}
$alreadyGenerated[$class] = true;
// register class' reflector for resource tracking
$this->container->getReflectionClass($class);
if ("\n" === $proxyCode = "\n".$proxyDumper->getProxyCode($definition)) {
continue;
}
if ($this->inlineRequires) {
$lineage = [];
$this->collectLineage($class, $lineage);
$code = '';
foreach (array_diff_key(array_flip($lineage), $this->inlinedRequires) as $file => $class) {
if ($this->inlineFactories) {
$this->inlinedRequires[$file] = true;
}
$code .= sprintf("include_once %s;\n", $file);
}
$proxyCode = $code.$proxyCode;
}
if ($strip) {
$proxyCode = "<?php\n".$proxyCode;
$proxyCode = substr(Kernel::stripComments($proxyCode), 5);
}
$proxyClass = explode(' ', $this->inlineRequires ? substr($proxyCode, \strlen($code)) : $proxyCode, 3)[1];
if ($this->asFiles || $this->namespace) {
$proxyCode .= "\nif (!\\class_exists('$proxyClass', false)) {\n \\class_alias(__NAMESPACE__.'\\\\$proxyClass', '$proxyClass', false);\n}\n";
}
$proxyClasses[$proxyClass.'.php'] = $proxyCode;
}
return $proxyClasses;
}
private function addServiceInclude(string $cId, Definition $definition): string
{
$code = '';
if ($this->inlineRequires && (!$this->isHotPath($definition) || $this->getProxyDumper()->isProxyCandidate($definition))) {
$lineage = [];
foreach ($this->inlinedDefinitions as $def) {
if (!$def->isDeprecated()) {
foreach ($this->getClasses($def, $cId) as $class) {
$this->collectLineage($class, $lineage);
}
}
}
foreach ($this->serviceCalls as $id => [$callCount, $behavior]) {
if ('service_container' !== $id && $id !== $cId
&& ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE !== $behavior
&& $this->container->has($id)
&& $this->isTrivialInstance($def = $this->container->findDefinition($id))
) {
foreach ($this->getClasses($def, $cId) as $class) {
$this->collectLineage($class, $lineage);
}
}
}
foreach (array_diff_key(array_flip($lineage), $this->inlinedRequires) as $file => $class) {
$code .= sprintf(" include_once %s;\n", $file);
}
}
foreach ($this->inlinedDefinitions as $def) {
if ($file = $def->getFile()) {
$file = $this->dumpValue($file);
$file = '(' === $file[0] ? substr($file, 1, -1) : $file;
$code .= sprintf(" include_once %s;\n", $file);
}
}
if ('' !== $code) {
$code .= "\n";
}
return $code;
}
/**
* @throws InvalidArgumentException
* @throws RuntimeException
*/
private function addServiceInstance(string $id, Definition $definition, bool $isSimpleInstance): string
{
$class = $this->dumpValue($definition->getClass());
if (0 === strpos($class, "'") && false === strpos($class, '$') && !preg_match('/^\'(?:\\\{2})?[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*(?:\\\{2}[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)*\'$/', $class)) {
throw new InvalidArgumentException(sprintf('"%s" is not a valid class name for the "%s" service.', $class, $id));
}
$isProxyCandidate = $this->getProxyDumper()->isProxyCandidate($definition);
$instantiation = '';
$lastWitherIndex = null;
foreach ($definition->getMethodCalls() as $k => $call) {
if ($call[2] ?? false) {
$lastWitherIndex = $k;
}
}
if (!$isProxyCandidate && $definition->isShared() && !isset($this->singleUsePrivateIds[$id]) && null === $lastWitherIndex) {
$instantiation = sprintf('$this->%s[%s] = %s', $this->container->getDefinition($id)->isPublic() ? 'services' : 'privates', $this->doExport($id), $isSimpleInstance ? '' : '$instance');
} elseif (!$isSimpleInstance) {
$instantiation = '$instance';
}
$return = '';
if ($isSimpleInstance) {
$return = 'return ';
} else {
$instantiation .= ' = ';
}
return $this->addNewInstance($definition, ' '.$return.$instantiation, $id);
}
private function isTrivialInstance(Definition $definition): bool
{
if ($definition->hasErrors()) {
return true;
}
if ($definition->isSynthetic() || $definition->getFile() || $definition->getMethodCalls() || $definition->getProperties() || $definition->getConfigurator()) {
return false;
}
if ($definition->isDeprecated() || $definition->isLazy() || $definition->getFactory() || 3 < \count($definition->getArguments())) {
return false;
}
foreach ($definition->getArguments() as $arg) {
if (!$arg || $arg instanceof Parameter) {
continue;
}
if (\is_array($arg) && 3 >= \count($arg)) {
foreach ($arg as $k => $v) {
if ($this->dumpValue($k) !== $this->dumpValue($k, false)) {
return false;
}
if (!$v || $v instanceof Parameter) {
continue;
}
if ($v instanceof Reference && $this->container->has($id = (string) $v) && $this->container->findDefinition($id)->isSynthetic()) {
continue;
}
if (!is_scalar($v) || $this->dumpValue($v) !== $this->dumpValue($v, false)) {
return false;
}
}
} elseif ($arg instanceof Reference && $this->container->has($id = (string) $arg) && $this->container->findDefinition($id)->isSynthetic()) {
continue;
} elseif (!is_scalar($arg) || $this->dumpValue($arg) !== $this->dumpValue($arg, false)) {
return false;
}
}
return true;
}
private function addServiceMethodCalls(Definition $definition, string $variableName, ?string $sharedNonLazyId): string
{
$lastWitherIndex = null;
foreach ($definition->getMethodCalls() as $k => $call) {
if ($call[2] ?? false) {
$lastWitherIndex = $k;
}
}
$calls = '';
foreach ($definition->getMethodCalls() as $k => $call) {
$arguments = [];
foreach ($call[1] as $value) {
$arguments[] = $this->dumpValue($value);
}
$witherAssignation = '';
if ($call[2] ?? false) {
if (null !== $sharedNonLazyId && $lastWitherIndex === $k) {
$witherAssignation = sprintf('$this->%s[\'%s\'] = ', $definition->isPublic() ? 'services' : 'privates', $sharedNonLazyId);
}
$witherAssignation .= sprintf('$%s = ', $variableName);
}
$calls .= $this->wrapServiceConditionals($call[1], sprintf(" %s\$%s->%s(%s);\n", $witherAssignation, $variableName, $call[0], implode(', ', $arguments)));
}
return $calls;
}
private function addServiceProperties(Definition $definition, string $variableName = 'instance'): string
{
$code = '';
foreach ($definition->getProperties() as $name => $value) {
$code .= sprintf(" \$%s->%s = %s;\n", $variableName, $name, $this->dumpValue($value));
}
return $code;
}
private function addServiceConfigurator(Definition $definition, string $variableName = 'instance'): string
{
if (!$callable = $definition->getConfigurator()) {
return '';
}
if (\is_array($callable)) {
if ($callable[0] instanceof Reference
|| ($callable[0] instanceof Definition && $this->definitionVariables->contains($callable[0]))
) {
return sprintf(" %s->%s(\$%s);\n", $this->dumpValue($callable[0]), $callable[1], $variableName);
}
$class = $this->dumpValue($callable[0]);
// If the class is a string we can optimize away
if (0 === strpos($class, "'") && false === strpos($class, '$')) {
return sprintf(" %s::%s(\$%s);\n", $this->dumpLiteralClass($class), $callable[1], $variableName);
}
if (0 === strpos($class, 'new ')) {
return sprintf(" (%s)->%s(\$%s);\n", $this->dumpValue($callable[0]), $callable[1], $variableName);
}
return sprintf(" [%s, '%s'](\$%s);\n", $this->dumpValue($callable[0]), $callable[1], $variableName);
}
return sprintf(" %s(\$%s);\n", $callable, $variableName);
}
private function addService(string $id, Definition $definition): array
{
$this->definitionVariables = new \SplObjectStorage();
$this->referenceVariables = [];
$this->variableCount = 0;
$this->referenceVariables[$id] = new Variable('instance');
$return = [];
if ($class = $definition->getClass()) {
$class = $class instanceof Parameter ? '%'.$class.'%' : $this->container->resolveEnvPlaceholders($class);
$return[] = sprintf(0 === strpos($class, '%') ? '@return object A %1$s instance' : '@return \%s', ltrim($class, '\\'));
} elseif ($definition->getFactory()) {
$factory = $definition->getFactory();
if (\is_string($factory)) {
$return[] = sprintf('@return object An instance returned by %s()', $factory);
} elseif (\is_array($factory) && (\is_string($factory[0]) || $factory[0] instanceof Definition || $factory[0] instanceof Reference)) {
$class = $factory[0] instanceof Definition ? $factory[0]->getClass() : (string) $factory[0];
$class = $class instanceof Parameter ? '%'.$class.'%' : $this->container->resolveEnvPlaceholders($class);
$return[] = sprintf('@return object An instance returned by %s::%s()', $class, $factory[1]);
}
}
if ($definition->isDeprecated()) {
if ($return && 0 === strpos($return[\count($return) - 1], '@return')) {
$return[] = '';
}
$deprecation = $definition->getDeprecation($id);
$return[] = sprintf('@deprecated %s', ($deprecation['package'] || $deprecation['version'] ? "Since {$deprecation['package']} {$deprecation['version']}: " : '').$deprecation['message']);
}
$return = str_replace("\n * \n", "\n *\n", implode("\n * ", $return));
$return = $this->container->resolveEnvPlaceholders($return);
$shared = $definition->isShared() ? ' shared' : '';
$public = $definition->isPublic() ? 'public' : 'private';
$autowired = $definition->isAutowired() ? ' autowired' : '';
$asFile = $this->asFiles && !$this->inlineFactories && !$this->isHotPath($definition);
$methodName = $this->generateMethodName($id);
if ($asFile || $definition->isLazy()) {
$lazyInitialization = '$lazyLoad = true';
} else {
$lazyInitialization = '';
}
$code = <<<EOF
/*{$this->docStar}
* Gets the $public '$id'$shared$autowired service.
*
* $return
EOF;
$code = str_replace('*/', ' ', $code).<<<EOF
*/
protected function {$methodName}($lazyInitialization)
{
EOF;
if ($asFile) {
$file = $methodName.'.php';
$code = str_replace("protected function {$methodName}(", 'public static function do($container, ', $code);
} else {
$file = null;
}
if ($definition->hasErrors() && $e = $definition->getErrors()) {
$this->addThrow = true;
$code .= sprintf(" \$this->throw(%s);\n", $this->export(reset($e)));
} else {
$this->serviceCalls = [];
$this->inlinedDefinitions = $this->getDefinitionsFromArguments([$definition], null, $this->serviceCalls);
if ($definition->isDeprecated()) {
$deprecation = $definition->getDeprecation($id);
$code .= sprintf(" trigger_deprecation(%s, %s, %s);\n\n", $this->export($deprecation['package']), $this->export($deprecation['version']), $this->export($deprecation['message']));
} elseif ($definition->hasTag($this->hotPathTag) || !$definition->hasTag($this->preloadTags[1])) {
foreach ($this->inlinedDefinitions as $def) {
foreach ($this->getClasses($def, $id) as $class) {
$this->preload[$class] = $class;
}
}
}
if (!$definition->isShared()) {
$factory = sprintf('$this->factories%s[%s]', $definition->isPublic() ? '' : "['service_container']", $this->doExport($id));
}
if ($isProxyCandidate = $this->getProxyDumper()->isProxyCandidate($definition)) {
if (!$definition->isShared()) {
$code .= sprintf(' %s = %1$s ?? ', $factory);
if ($asFile) {
$code .= "function () {\n";
$code .= " return self::do(\$container);\n";
$code .= " };\n\n";
} else {
$code .= sprintf("\\Closure::fromCallable([\$this, '%s']);\n\n", $methodName);
}
}
$factoryCode = $asFile ? 'self::do($container, false)' : sprintf('$this->%s(false)', $methodName);
$factoryCode = $this->getProxyDumper()->getProxyFactoryCode($definition, $id, $factoryCode);
$code .= $asFile ? preg_replace('/function \(([^)]*+)\) {/', 'function (\1) use ($container) {', $factoryCode) : $factoryCode;
}
$c = $this->addServiceInclude($id, $definition);
if ('' !== $c && $isProxyCandidate && !$definition->isShared()) {
$c = implode("\n", array_map(function ($line) { return $line ? ' '.$line : $line; }, explode("\n", $c)));
$code .= " static \$include = true;\n\n";
$code .= " if (\$include) {\n";
$code .= $c;
$code .= " \$include = false;\n";
$code .= " }\n\n";
} else {
$code .= $c;
}
$c = $this->addInlineService($id, $definition);
if (!$isProxyCandidate && !$definition->isShared()) {
$c = implode("\n", array_map(function ($line) { return $line ? ' '.$line : $line; }, explode("\n", $c)));
$lazyloadInitialization = $definition->isLazy() ? '$lazyLoad = true' : '';
$c = sprintf(" %s = function (%s) {\n%s };\n\n return %1\$s();\n", $factory, $lazyloadInitialization, $c);
}
$code .= $c;
}
if ($asFile) {
$code = str_replace('$this', '$container', $code);
$code = str_replace('function () {', 'function () use ($container) {', $code);
$code = str_replace('function ($lazyLoad = true) {', 'function ($lazyLoad = true) use ($container) {', $code);
}
$code .= " }\n";
$this->definitionVariables = $this->inlinedDefinitions = null;
$this->referenceVariables = $this->serviceCalls = null;
return [$file, $code];
}
private function addInlineVariables(string $id, Definition $definition, array $arguments, bool $forConstructor): string
{
$code = '';
foreach ($arguments as $argument) {
if (\is_array($argument)) {
$code .= $this->addInlineVariables($id, $definition, $argument, $forConstructor);
} elseif ($argument instanceof Reference) {
$code .= $this->addInlineReference($id, $definition, $argument, $forConstructor);
} elseif ($argument instanceof Definition) {
$code .= $this->addInlineService($id, $definition, $argument, $forConstructor);
}
}
return $code;
}
private function addInlineReference(string $id, Definition $definition, string $targetId, bool $forConstructor): string
{
while ($this->container->hasAlias($targetId)) {
$targetId = (string) $this->container->getAlias($targetId);
}
[$callCount, $behavior] = $this->serviceCalls[$targetId];
if ($id === $targetId) {
return $this->addInlineService($id, $definition, $definition);
}
if ('service_container' === $targetId || isset($this->referenceVariables[$targetId])) {
return '';
}
if ($this->container->hasDefinition($targetId) && ($def = $this->container->getDefinition($targetId)) && !$def->isShared()) {
return '';
}
$hasSelfRef = isset($this->circularReferences[$id][$targetId]) && !isset($this->definitionVariables[$definition]);
if ($hasSelfRef && !$forConstructor && !$forConstructor = !$this->circularReferences[$id][$targetId]) {
$code = $this->addInlineService($id, $definition, $definition);
} else {
$code = '';
}
if (isset($this->referenceVariables[$targetId]) || (2 > $callCount && (!$hasSelfRef || !$forConstructor))) {
return $code;
}
$name = $this->getNextVariableName();
$this->referenceVariables[$targetId] = new Variable($name);
$reference = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE >= $behavior ? new Reference($targetId, $behavior) : null;
$code .= sprintf(" \$%s = %s;\n", $name, $this->getServiceCall($targetId, $reference));
if (!$hasSelfRef || !$forConstructor) {
return $code;
}
$code .= sprintf(<<<'EOTXT'
if (isset($this->%s[%s])) {
return $this->%1$s[%2$s];
}
EOTXT
,
$this->container->getDefinition($id)->isPublic() ? 'services' : 'privates',
$this->doExport($id)
);
return $code;
}
private function addInlineService(string $id, Definition $definition, Definition $inlineDef = null, bool $forConstructor = true): string
{
$code = '';
if ($isSimpleInstance = $isRootInstance = null === $inlineDef) {
foreach ($this->serviceCalls as $targetId => [$callCount, $behavior, $byConstructor]) {
if ($byConstructor && isset($this->circularReferences[$id][$targetId]) && !$this->circularReferences[$id][$targetId]) {
$code .= $this->addInlineReference($id, $definition, $targetId, $forConstructor);
}
}
}
if (isset($this->definitionVariables[$inlineDef = $inlineDef ?: $definition])) {
return $code;
}
$arguments = [$inlineDef->getArguments(), $inlineDef->getFactory()];
$code .= $this->addInlineVariables($id, $definition, $arguments, $forConstructor);
if ($arguments = array_filter([$inlineDef->getProperties(), $inlineDef->getMethodCalls(), $inlineDef->getConfigurator()])) {
$isSimpleInstance = false;
} elseif ($definition !== $inlineDef && 2 > $this->inlinedDefinitions[$inlineDef]) {
return $code;
}
if (isset($this->definitionVariables[$inlineDef])) {
$isSimpleInstance = false;
} else {
$name = $definition === $inlineDef ? 'instance' : $this->getNextVariableName();
$this->definitionVariables[$inlineDef] = new Variable($name);
$code .= '' !== $code ? "\n" : '';
if ('instance' === $name) {
$code .= $this->addServiceInstance($id, $definition, $isSimpleInstance);
} else {
$code .= $this->addNewInstance($inlineDef, ' $'.$name.' = ', $id);
}
if ('' !== $inline = $this->addInlineVariables($id, $definition, $arguments, false)) {
$code .= "\n".$inline."\n";
} elseif ($arguments && 'instance' === $name) {
$code .= "\n";
}
$code .= $this->addServiceProperties($inlineDef, $name);
$code .= $this->addServiceMethodCalls($inlineDef, $name, !$this->getProxyDumper()->isProxyCandidate($inlineDef) && $inlineDef->isShared() && !isset($this->singleUsePrivateIds[$id]) ? $id : null);
$code .= $this->addServiceConfigurator($inlineDef, $name);
}
if ($isRootInstance && !$isSimpleInstance) {
$code .= "\n return \$instance;\n";
}
return $code;
}
private function addServices(array &$services = null): string
{
$publicServices = $privateServices = '';
$definitions = $this->container->getDefinitions();
ksort($definitions);
foreach ($definitions as $id => $definition) {
if (!$definition->isSynthetic()) {
$services[$id] = $this->addService($id, $definition);
} elseif ($definition->hasTag($this->hotPathTag) || !$definition->hasTag($this->preloadTags[1])) {
$services[$id] = null;
foreach ($this->getClasses($definition, $id) as $class) {
$this->preload[$class] = $class;
}
}
}
foreach ($definitions as $id => $definition) {
if (!([$file, $code] = $services[$id]) || null !== $file) {
continue;
}
if ($definition->isPublic()) {
$publicServices .= $code;
} elseif (!$this->isTrivialInstance($definition) || isset($this->locatedIds[$id])) {
$privateServices .= $code;
}
}
return $publicServices.$privateServices;
}
private function generateServiceFiles(array $services): iterable
{
$definitions = $this->container->getDefinitions();
ksort($definitions);
foreach ($definitions as $id => $definition) {
if (([$file, $code] = $services[$id]) && null !== $file && ($definition->isPublic() || !$this->isTrivialInstance($definition) || isset($this->locatedIds[$id]))) {
yield $file => [$code, $definition->hasTag($this->hotPathTag) || !$definition->hasTag($this->preloadTags[1]) && !$definition->isDeprecated() && !$definition->hasErrors()];
}
}
}
private function addNewInstance(Definition $definition, string $return = '', string $id = null): string
{
$tail = $return ? ";\n" : '';
if (BaseServiceLocator::class === $definition->getClass() && $definition->hasTag($this->serviceLocatorTag)) {
$arguments = [];
foreach ($definition->getArgument(0) as $k => $argument) {
$arguments[$k] = $argument->getValues()[0];
}
return $return.$this->dumpValue(new ServiceLocatorArgument($arguments)).$tail;
}
$arguments = [];
foreach ($definition->getArguments() as $value) {
$arguments[] = $this->dumpValue($value);
}
if (null !== $definition->getFactory()) {
$callable = $definition->getFactory();
if (\is_array($callable)) {
if (!preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $callable[1])) {
throw new RuntimeException(sprintf('Cannot dump definition because of invalid factory method (%s).', $callable[1] ?: 'n/a'));
}
if ($callable[0] instanceof Reference
|| ($callable[0] instanceof Definition && $this->definitionVariables->contains($callable[0]))) {
return $return.sprintf('%s->%s(%s)', $this->dumpValue($callable[0]), $callable[1], $arguments ? implode(', ', $arguments) : '').$tail;
}
$class = $this->dumpValue($callable[0]);
// If the class is a string we can optimize away
if (0 === strpos($class, "'") && false === strpos($class, '$')) {
if ("''" === $class) {
throw new RuntimeException(sprintf('Cannot dump definition: "%s" service is defined to be created by a factory but is missing the service reference, did you forget to define the factory service id or class?', $id ? 'The "'.$id.'"' : 'inline'));
}
return $return.sprintf('%s::%s(%s)', $this->dumpLiteralClass($class), $callable[1], $arguments ? implode(', ', $arguments) : '').$tail;
}
if (0 === strpos($class, 'new ')) {
return $return.sprintf('(%s)->%s(%s)', $class, $callable[1], $arguments ? implode(', ', $arguments) : '').$tail;
}
return $return.sprintf("[%s, '%s'](%s)", $class, $callable[1], $arguments ? implode(', ', $arguments) : '').$tail;
}
return $return.sprintf('%s(%s)', $this->dumpLiteralClass($this->dumpValue($callable)), $arguments ? implode(', ', $arguments) : '').$tail;
}
if (null === $class = $definition->getClass()) {
throw new RuntimeException('Cannot dump definitions which have no class nor factory.');
}
return $return.sprintf('new %s(%s)', $this->dumpLiteralClass($this->dumpValue($class)), implode(', ', $arguments)).$tail;
}
private function startClass(string $class, string $baseClass): string
{
$namespaceLine = !$this->asFiles && $this->namespace ? "\nnamespace {$this->namespace};\n" : '';
$code = <<<EOF
<?php
$namespaceLine
use Symfony\Component\DependencyInjection\Argument\RewindableGenerator;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Exception\LogicException;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
use Symfony\Component\DependencyInjection\ParameterBag\FrozenParameterBag;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
/*{$this->docStar}
* @internal This class has been auto-generated by the Symfony Dependency Injection Component.
*/
class $class extends $baseClass
{
protected \$parameters = [];
public function __construct()
{
EOF;
if ($this->asFiles) {
$code = str_replace('$parameters = []', "\$containerDir;\n protected \$parameters = [];\n private \$buildParameters", $code);
$code = str_replace('__construct()', '__construct(array $buildParameters = [], $containerDir = __DIR__)', $code);
$code .= " \$this->buildParameters = \$buildParameters;\n";
$code .= " \$this->containerDir = \$containerDir;\n";
if (null !== $this->targetDirRegex) {
$code = str_replace('$parameters = []', "\$targetDir;\n protected \$parameters = []", $code);
$code .= ' $this->targetDir = \\dirname($containerDir);'."\n";
}
}
if (Container::class !== $this->baseClass) {
$r = $this->container->getReflectionClass($this->baseClass, false);
if (null !== $r
&& (null !== $constructor = $r->getConstructor())
&& 0 === $constructor->getNumberOfRequiredParameters()
&& Container::class !== $constructor->getDeclaringClass()->name
) {
$code .= " parent::__construct();\n";
$code .= " \$this->parameterBag = null;\n\n";
}
}
if ($this->container->getParameterBag()->all()) {
$code .= " \$this->parameters = \$this->getDefaultParameters();\n\n";
}
$code .= " \$this->services = \$this->privates = [];\n";
$code .= $this->addSyntheticIds();
$code .= $this->addMethodMap();
$code .= $this->asFiles && !$this->inlineFactories ? $this->addFileMap() : '';
$code .= $this->addAliases();
$code .= $this->addInlineRequires();
$code .= <<<EOF
}
public function compile(): void
{
throw new LogicException('You cannot compile a dumped container that was already compiled.');
}
public function isCompiled(): bool
{
return true;
}
EOF;
$code .= $this->addRemovedIds();
if ($this->asFiles && !$this->inlineFactories) {
$code .= <<<'EOF'
protected function load($file, $lazyLoad = true)
{
if (class_exists($class = __NAMESPACE__.'\\'.$file, false)) {
return $class::do($this, $lazyLoad);
}
if ('.' === $file[-4]) {
$class = substr($class, 0, -4);
} else {
$file .= '.php';
}
$service = require $this->containerDir.\DIRECTORY_SEPARATOR.$file;
return class_exists($class, false) ? $class::do($this, $lazyLoad) : $service;
}
EOF;
}
$proxyDumper = $this->getProxyDumper();
foreach ($this->container->getDefinitions() as $definition) {
if (!$proxyDumper->isProxyCandidate($definition)) {
continue;
}
if ($this->asFiles && !$this->inlineFactories) {
$proxyLoader = "class_exists(\$class, false) || require __DIR__.'/'.\$class.'.php';\n\n ";
} else {
$proxyLoader = '';
}
$code .= <<<EOF
protected function createProxy(\$class, \Closure \$factory)
{
{$proxyLoader}return \$factory();
}
EOF;
break;
}
return $code;
}
private function addSyntheticIds(): string
{
$code = '';
$definitions = $this->container->getDefinitions();
ksort($definitions);
foreach ($definitions as $id => $definition) {
if ($definition->isSynthetic() && 'service_container' !== $id) {
$code .= ' '.$this->doExport($id)." => true,\n";
}
}
return $code ? " \$this->syntheticIds = [\n{$code} ];\n" : '';
}
private function addRemovedIds(): string
{
$ids = $this->container->getRemovedIds();
foreach ($this->container->getDefinitions() as $id => $definition) {
if (!$definition->isPublic()) {
$ids[$id] = true;
}
}
if (!$ids) {
return '';
}
if ($this->asFiles) {
$code = "require \$this->containerDir.\\DIRECTORY_SEPARATOR.'removed-ids.php'";
} else {
$code = '';
$ids = array_keys($ids);
sort($ids);
foreach ($ids as $id) {
if (preg_match(FileLoader::ANONYMOUS_ID_REGEXP, $id)) {
continue;
}
$code .= ' '.$this->doExport($id)." => true,\n";
}
$code = "[\n{$code} ]";
}
return <<<EOF
public function getRemovedIds(): array
{
return {$code};
}
EOF;
}
private function addMethodMap(): string
{
$code = '';
$definitions = $this->container->getDefinitions();
ksort($definitions);
foreach ($definitions as $id => $definition) {
if (!$definition->isSynthetic() && $definition->isPublic() && (!$this->asFiles || $this->inlineFactories || $this->isHotPath($definition))) {
$code .= ' '.$this->doExport($id).' => '.$this->doExport($this->generateMethodName($id)).",\n";
}
}
$aliases = $this->container->getAliases();
foreach ($aliases as $alias => $id) {
if (!$id->isDeprecated()) {
continue;
}
$code .= ' '.$this->doExport($alias).' => '.$this->doExport($this->generateMethodName($alias)).",\n";
}
return $code ? " \$this->methodMap = [\n{$code} ];\n" : '';
}
private function addFileMap(): string
{
$code = '';
$definitions = $this->container->getDefinitions();
ksort($definitions);
foreach ($definitions as $id => $definition) {
if (!$definition->isSynthetic() && $definition->isPublic() && !$this->isHotPath($definition)) {
$code .= sprintf(" %s => '%s',\n", $this->doExport($id), $this->generateMethodName($id));
}
}
return $code ? " \$this->fileMap = [\n{$code} ];\n" : '';
}
private function addAliases(): string
{
if (!$aliases = $this->container->getAliases()) {
return "\n \$this->aliases = [];\n";
}
$code = " \$this->aliases = [\n";
ksort($aliases);
foreach ($aliases as $alias => $id) {
if ($id->isDeprecated()) {
continue;
}
$id = (string) $id;
while (isset($aliases[$id])) {
$id = (string) $aliases[$id];
}
$code .= ' '.$this->doExport($alias).' => '.$this->doExport($id).",\n";
}
return $code." ];\n";
}
private function addDeprecatedAliases(): string
{
$code = '';
$aliases = $this->container->getAliases();
foreach ($aliases as $alias => $definition) {
if (!$definition->isDeprecated()) {
continue;
}
$public = $definition->isPublic() ? 'public' : 'private';
$id = (string) $definition;
$methodNameAlias = $this->generateMethodName($alias);
$idExported = $this->export($id);
$deprecation = $definition->getDeprecation($alias);
$packageExported = $this->export($deprecation['package']);
$versionExported = $this->export($deprecation['version']);
$messageExported = $this->export($deprecation['message']);
$code .= <<<EOF
/*{$this->docStar}
* Gets the $public '$alias' alias.
*
* @return object The "$id" service.
*/
protected function {$methodNameAlias}()
{
trigger_deprecation($packageExported, $versionExported, $messageExported);
return \$this->get($idExported);
}
EOF;
}
return $code;
}
private function addInlineRequires(): string
{
if (!$this->hotPathTag || !$this->inlineRequires) {
return '';
}
$lineage = [];
foreach ($this->container->findTaggedServiceIds($this->hotPathTag) as $id => $tags) {
$definition = $this->container->getDefinition($id);
if ($this->getProxyDumper()->isProxyCandidate($definition)) {
continue;
}
$inlinedDefinitions = $this->getDefinitionsFromArguments([$definition]);
foreach ($inlinedDefinitions as $def) {
foreach ($this->getClasses($def, $id) as $class) {
$this->collectLineage($class, $lineage);
}
}
}
$code = '';
foreach ($lineage as $file) {
if (!isset($this->inlinedRequires[$file])) {
$this->inlinedRequires[$file] = true;
$code .= sprintf("\n include_once %s;", $file);
}
}
return $code ? sprintf("\n \$this->privates['service_container'] = function () {%s\n };\n", $code) : '';
}
private function addDefaultParametersMethod(): string
{
if (!$this->container->getParameterBag()->all()) {
return '';
}
$php = [];
$dynamicPhp = [];
foreach ($this->container->getParameterBag()->all() as $key => $value) {
if ($key !== $resolvedKey = $this->container->resolveEnvPlaceholders($key)) {
throw new InvalidArgumentException(sprintf('Parameter name cannot use env parameters: "%s".', $resolvedKey));
}
$export = $this->exportParameters([$value]);
$export = explode('0 => ', substr(rtrim($export, " ]\n"), 2, -1), 2);
if (preg_match("/\\\$this->(?:getEnv\('(?:[-.\w]*+:)*+\w++'\)|targetDir\.'')/", $export[1])) {
$dynamicPhp[$key] = sprintf('%scase %s: $value = %s; break;', $export[0], $this->export($key), $export[1]);
} else {
$php[] = sprintf('%s%s => %s,', $export[0], $this->export($key), $export[1]);
}
}
$parameters = sprintf("[\n%s\n%s]", implode("\n", $php), str_repeat(' ', 8));
$code = <<<'EOF'
public function getParameter(string $name)
{
if (isset($this->buildParameters[$name])) {
return $this->buildParameters[$name];
}
if (!(isset($this->parameters[$name]) || isset($this->loadedDynamicParameters[$name]) || \array_key_exists($name, $this->parameters))) {
throw new InvalidArgumentException(sprintf('The parameter "%s" must be defined.', $name));
}
if (isset($this->loadedDynamicParameters[$name])) {
return $this->loadedDynamicParameters[$name] ? $this->dynamicParameters[$name] : $this->getDynamicParameter($name);
}
return $this->parameters[$name];
}
public function hasParameter(string $name): bool
{
if (isset($this->buildParameters[$name])) {
return true;
}
return isset($this->parameters[$name]) || isset($this->loadedDynamicParameters[$name]) || \array_key_exists($name, $this->parameters);
}
public function setParameter(string $name, $value): void
{
throw new LogicException('Impossible to call set() on a frozen ParameterBag.');
}
public function getParameterBag(): ParameterBagInterface
{
if (null === $this->parameterBag) {
$parameters = $this->parameters;
foreach ($this->loadedDynamicParameters as $name => $loaded) {
$parameters[$name] = $loaded ? $this->dynamicParameters[$name] : $this->getDynamicParameter($name);
}
foreach ($this->buildParameters as $name => $value) {
$parameters[$name] = $value;
}
$this->parameterBag = new FrozenParameterBag($parameters);
}
return $this->parameterBag;
}
EOF;
if (!$this->asFiles) {
$code = preg_replace('/^.*buildParameters.*\n.*\n.*\n\n?/m', '', $code);
}
if ($dynamicPhp) {
$loadedDynamicParameters = $this->exportParameters(array_combine(array_keys($dynamicPhp), array_fill(0, \count($dynamicPhp), false)), '', 8);
$getDynamicParameter = <<<'EOF'
switch ($name) {
%s
default: throw new InvalidArgumentException(sprintf('The dynamic parameter "%%s" must be defined.', $name));
}
$this->loadedDynamicParameters[$name] = true;
return $this->dynamicParameters[$name] = $value;
EOF;
$getDynamicParameter = sprintf($getDynamicParameter, implode("\n", $dynamicPhp));
} else {
$loadedDynamicParameters = '[]';
$getDynamicParameter = str_repeat(' ', 8).'throw new InvalidArgumentException(sprintf(\'The dynamic parameter "%s" must be defined.\', $name));';
}
$code .= <<<EOF
private \$loadedDynamicParameters = {$loadedDynamicParameters};
private \$dynamicParameters = [];
private function getDynamicParameter(string \$name)
{
{$getDynamicParameter}
}
protected function getDefaultParameters(): array
{
return $parameters;
}
EOF;
return $code;
}
/**
* @throws InvalidArgumentException
*/
private function exportParameters(array $parameters, string $path = '', int $indent = 12): string
{
$php = [];
foreach ($parameters as $key => $value) {
if (\is_array($value)) {
$value = $this->exportParameters($value, $path.'/'.$key, $indent + 4);
} elseif ($value instanceof ArgumentInterface) {
throw new InvalidArgumentException(sprintf('You cannot dump a container with parameters that contain special arguments. "%s" found in "%s".', get_debug_type($value), $path.'/'.$key));
} elseif ($value instanceof Variable) {
throw new InvalidArgumentException(sprintf('You cannot dump a container with parameters that contain variable references. Variable "%s" found in "%s".', $value, $path.'/'.$key));
} elseif ($value instanceof Definition) {
throw new InvalidArgumentException(sprintf('You cannot dump a container with parameters that contain service definitions. Definition for "%s" found in "%s".', $value->getClass(), $path.'/'.$key));
} elseif ($value instanceof Reference) {
throw new InvalidArgumentException(sprintf('You cannot dump a container with parameters that contain references to other services (reference to service "%s" found in "%s").', $value, $path.'/'.$key));
} elseif ($value instanceof Expression) {
throw new InvalidArgumentException(sprintf('You cannot dump a container with parameters that contain expressions. Expression "%s" found in "%s".', $value, $path.'/'.$key));
} else {
$value = $this->export($value);
}
$php[] = sprintf('%s%s => %s,', str_repeat(' ', $indent), $this->export($key), $value);
}
return sprintf("[\n%s\n%s]", implode("\n", $php), str_repeat(' ', $indent - 4));
}
private function endClass(): string
{
if ($this->addThrow) {
return <<<'EOF'
protected function throw($message)
{
throw new RuntimeException($message);
}
}
EOF;
}
return <<<'EOF'
}
EOF;
}
private function wrapServiceConditionals($value, string $code): string
{
if (!$condition = $this->getServiceConditionals($value)) {
return $code;
}
// re-indent the wrapped code
$code = implode("\n", array_map(function ($line) { return $line ? ' '.$line : $line; }, explode("\n", $code)));
return sprintf(" if (%s) {\n%s }\n", $condition, $code);
}
private function getServiceConditionals($value): string
{
$conditions = [];
foreach (ContainerBuilder::getInitializedConditionals($value) as $service) {
if (!$this->container->hasDefinition($service)) {
return 'false';
}
$conditions[] = sprintf('isset($this->%s[%s])', $this->container->getDefinition($service)->isPublic() ? 'services' : 'privates', $this->doExport($service));
}
foreach (ContainerBuilder::getServiceConditionals($value) as $service) {
if ($this->container->hasDefinition($service) && !$this->container->getDefinition($service)->isPublic()) {
continue;
}
$conditions[] = sprintf('$this->has(%s)', $this->doExport($service));
}
if (!$conditions) {
return '';
}
return implode(' && ', $conditions);
}
private function getDefinitionsFromArguments(array $arguments, \SplObjectStorage $definitions = null, array &$calls = [], bool $byConstructor = null): \SplObjectStorage
{
if (null === $definitions) {
$definitions = new \SplObjectStorage();
}
foreach ($arguments as $argument) {
if (\is_array($argument)) {
$this->getDefinitionsFromArguments($argument, $definitions, $calls, $byConstructor);
} elseif ($argument instanceof Reference) {
$id = (string) $argument;
while ($this->container->hasAlias($id)) {
$id = (string) $this->container->getAlias($id);
}
if (!isset($calls[$id])) {
$calls[$id] = [0, $argument->getInvalidBehavior(), $byConstructor];
} else {
$calls[$id][1] = min($calls[$id][1], $argument->getInvalidBehavior());
}
++$calls[$id][0];
} elseif (!$argument instanceof Definition) {
// no-op
} elseif (isset($definitions[$argument])) {
$definitions[$argument] = 1 + $definitions[$argument];
} else {
$definitions[$argument] = 1;
$arguments = [$argument->getArguments(), $argument->getFactory()];
$this->getDefinitionsFromArguments($arguments, $definitions, $calls, null === $byConstructor || $byConstructor);
$arguments = [$argument->getProperties(), $argument->getMethodCalls(), $argument->getConfigurator()];
$this->getDefinitionsFromArguments($arguments, $definitions, $calls, null !== $byConstructor && $byConstructor);
}
}
return $definitions;
}
/**
* @throws RuntimeException
*/
private function dumpValue($value, bool $interpolate = true): string
{
if (\is_array($value)) {
if ($value && $interpolate && false !== $param = array_search($value, $this->container->getParameterBag()->all(), true)) {
return $this->dumpValue("%$param%");
}
$code = [];
foreach ($value as $k => $v) {
$code[] = sprintf('%s => %s', $this->dumpValue($k, $interpolate), $this->dumpValue($v, $interpolate));
}
return sprintf('[%s]', implode(', ', $code));
} elseif ($value instanceof ArgumentInterface) {
$scope = [$this->definitionVariables, $this->referenceVariables];
$this->definitionVariables = $this->referenceVariables = null;
try {
if ($value instanceof ServiceClosureArgument) {
$value = $value->getValues()[0];
$code = $this->dumpValue($value, $interpolate);
$returnedType = '';
if ($value instanceof TypedReference) {
$returnedType = sprintf(': %s\%s', ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE >= $value->getInvalidBehavior() ? '' : '?', $value->getType());
}
$code = sprintf('return %s;', $code);
return sprintf("function ()%s {\n %s\n }", $returnedType, $code);
}
if ($value instanceof IteratorArgument) {
$operands = [0];
$code = [];
$code[] = 'new RewindableGenerator(function () {';
if (!$values = $value->getValues()) {
$code[] = ' return new \EmptyIterator();';
} else {
$countCode = [];
$countCode[] = 'function () {';
foreach ($values as $k => $v) {
($c = $this->getServiceConditionals($v)) ? $operands[] = "(int) ($c)" : ++$operands[0];
$v = $this->wrapServiceConditionals($v, sprintf(" yield %s => %s;\n", $this->dumpValue($k, $interpolate), $this->dumpValue($v, $interpolate)));
foreach (explode("\n", $v) as $v) {
if ($v) {
$code[] = ' '.$v;
}
}
}
$countCode[] = sprintf(' return %s;', implode(' + ', $operands));
$countCode[] = ' }';
}
$code[] = sprintf(' }, %s)', \count($operands) > 1 ? implode("\n", $countCode) : $operands[0]);
return implode("\n", $code);
}
if ($value instanceof ServiceLocatorArgument) {
$serviceMap = '';
$serviceTypes = '';
foreach ($value->getValues() as $k => $v) {
if (!$v) {
continue;
}
$id = (string) $v;
while ($this->container->hasAlias($id)) {
$id = (string) $this->container->getAlias($id);
}
$definition = $this->container->getDefinition($id);
$load = !($definition->hasErrors() && $e = $definition->getErrors()) ? $this->asFiles && !$this->inlineFactories && !$this->isHotPath($definition) : reset($e);
$serviceMap .= sprintf("\n %s => [%s, %s, %s, %s],",
$this->export($k),
$this->export($definition->isShared() ? ($definition->isPublic() ? 'services' : 'privates') : false),
$this->doExport($id),
$this->export(ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE !== $v->getInvalidBehavior() && !\is_string($load) ? $this->generateMethodName($id) : null),
$this->export($load)
);
$serviceTypes .= sprintf("\n %s => %s,", $this->export($k), $this->export($v instanceof TypedReference ? $v->getType() : '?'));
$this->locatedIds[$id] = true;
}
$this->addGetService = true;
return sprintf('new \%s($this->getService, [%s%s], [%s%s])', ServiceLocator::class, $serviceMap, $serviceMap ? "\n " : '', $serviceTypes, $serviceTypes ? "\n " : '');
}
} finally {
[$this->definitionVariables, $this->referenceVariables] = $scope;
}
} elseif ($value instanceof Definition) {
if ($value->hasErrors() && $e = $value->getErrors()) {
$this->addThrow = true;
return sprintf('$this->throw(%s)', $this->export(reset($e)));
}
if (null !== $this->definitionVariables && $this->definitionVariables->contains($value)) {
return $this->dumpValue($this->definitionVariables[$value], $interpolate);
}
if ($value->getMethodCalls()) {
throw new RuntimeException('Cannot dump definitions which have method calls.');
}
if ($value->getProperties()) {
throw new RuntimeException('Cannot dump definitions which have properties.');
}
if (null !== $value->getConfigurator()) {
throw new RuntimeException('Cannot dump definitions which have a configurator.');
}
return $this->addNewInstance($value);
} elseif ($value instanceof Variable) {
return '$'.$value;
} elseif ($value instanceof Reference) {
$id = (string) $value;
while ($this->container->hasAlias($id)) {
$id = (string) $this->container->getAlias($id);
}
if (null !== $this->referenceVariables && isset($this->referenceVariables[$id])) {
return $this->dumpValue($this->referenceVariables[$id], $interpolate);
}
return $this->getServiceCall($id, $value);
} elseif ($value instanceof Expression) {
return $this->getExpressionLanguage()->compile((string) $value, ['this' => 'container']);
} elseif ($value instanceof Parameter) {
return $this->dumpParameter($value);
} elseif (true === $interpolate && \is_string($value)) {
if (preg_match('/^%([^%]+)%$/', $value, $match)) {
// we do this to deal with non string values (Boolean, integer, ...)
// the preg_replace_callback converts them to strings
return $this->dumpParameter($match[1]);
} else {
$replaceParameters = function ($match) {
return "'.".$this->dumpParameter($match[2]).".'";
};
$code = str_replace('%%', '%', preg_replace_callback('/(?<!%)(%)([^%]+)\1/', $replaceParameters, $this->export($value)));
return $code;
}
} elseif ($value instanceof AbstractArgument) {
throw new RuntimeException($value->getTextWithContext());
} elseif (\is_object($value) || \is_resource($value)) {
throw new RuntimeException('Unable to dump a service container if a parameter is an object or a resource.');
}
return $this->export($value);
}
/**
* Dumps a string to a literal (aka PHP Code) class value.
*
* @throws RuntimeException
*/
private function dumpLiteralClass(string $class): string
{
if (false !== strpos($class, '$')) {
return sprintf('${($_ = %s) && false ?: "_"}', $class);
}
if (0 !== strpos($class, "'") || !preg_match('/^\'(?:\\\{2})?[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*(?:\\\{2}[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)*\'$/', $class)) {
throw new RuntimeException(sprintf('Cannot dump definition because of invalid class name (%s).', $class ?: 'n/a'));
}
$class = substr(str_replace('\\\\', '\\', $class), 1, -1);
return 0 === strpos($class, '\\') ? $class : '\\'.$class;
}
private function dumpParameter(string $name): string
{
if ($this->container->hasParameter($name)) {
$value = $this->container->getParameter($name);
$dumpedValue = $this->dumpValue($value, false);
if (!$value || !\is_array($value)) {
return $dumpedValue;
}
if (!preg_match("/\\\$this->(?:getEnv\('(?:[-.\w]*+:)*+\w++'\)|targetDir\.'')/", $dumpedValue)) {
return sprintf('$this->parameters[%s]', $this->doExport($name));
}
}
return sprintf('$this->getParameter(%s)', $this->doExport($name));
}
private function getServiceCall(string $id, Reference $reference = null): string
{
while ($this->container->hasAlias($id)) {
$id = (string) $this->container->getAlias($id);
}
if ('service_container' === $id) {
return '$this';
}
if ($this->container->hasDefinition($id) && $definition = $this->container->getDefinition($id)) {
if ($definition->isSynthetic()) {
$code = sprintf('$this->get(%s%s)', $this->doExport($id), null !== $reference ? ', '.$reference->getInvalidBehavior() : '');
} elseif (null !== $reference && ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE === $reference->getInvalidBehavior()) {
$code = 'null';
if (!$definition->isShared()) {
return $code;
}
} elseif ($this->isTrivialInstance($definition)) {
if ($definition->hasErrors() && $e = $definition->getErrors()) {
$this->addThrow = true;
return sprintf('$this->throw(%s)', $this->export(reset($e)));
}
$code = $this->addNewInstance($definition, '', $id);
if ($definition->isShared() && !isset($this->singleUsePrivateIds[$id])) {
$code = sprintf('$this->%s[%s] = %s', $definition->isPublic() ? 'services' : 'privates', $this->doExport($id), $code);
}
$code = "($code)";
} else {
$code = $this->asFiles && !$this->inlineFactories && !$this->isHotPath($definition) ? "\$this->load('%s')" : '$this->%s()';
$code = sprintf($code, $this->generateMethodName($id));
if (!$definition->isShared()) {
$factory = sprintf('$this->factories%s[%s]', $definition->isPublic() ? '' : "['service_container']", $this->doExport($id));
$code = sprintf('(isset(%s) ? %1$s() : %s)', $factory, $code);
}
}
if ($definition->isShared() && !isset($this->singleUsePrivateIds[$id])) {
$code = sprintf('($this->%s[%s] ?? %s)', $definition->isPublic() ? 'services' : 'privates', $this->doExport($id), $code);
}
return $code;
}
if (null !== $reference && ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE === $reference->getInvalidBehavior()) {
return 'null';
}
if (null !== $reference && ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE < $reference->getInvalidBehavior()) {
$code = sprintf('$this->get(%s, /* ContainerInterface::NULL_ON_INVALID_REFERENCE */ %d)', $this->doExport($id), ContainerInterface::NULL_ON_INVALID_REFERENCE);
} else {
$code = sprintf('$this->get(%s)', $this->doExport($id));
}
return sprintf('($this->services[%s] ?? %s)', $this->doExport($id), $code);
}
/**
* Initializes the method names map to avoid conflicts with the Container methods.
*/
private function initializeMethodNamesMap(string $class)
{
$this->serviceIdToMethodNameMap = [];
$this->usedMethodNames = [];
if ($reflectionClass = $this->container->getReflectionClass($class)) {
foreach ($reflectionClass->getMethods() as $method) {
$this->usedMethodNames[strtolower($method->getName())] = true;
}
}
}
/**
* @throws InvalidArgumentException
*/
private function generateMethodName(string $id): string
{
if (isset($this->serviceIdToMethodNameMap[$id])) {
return $this->serviceIdToMethodNameMap[$id];
}
$i = strrpos($id, '\\');
$name = Container::camelize(false !== $i && isset($id[1 + $i]) ? substr($id, 1 + $i) : $id);
$name = preg_replace('/[^a-zA-Z0-9_\x7f-\xff]/', '', $name);
$methodName = 'get'.$name.'Service';
$suffix = 1;
while (isset($this->usedMethodNames[strtolower($methodName)])) {
++$suffix;
$methodName = 'get'.$name.$suffix.'Service';
}
$this->serviceIdToMethodNameMap[$id] = $methodName;
$this->usedMethodNames[strtolower($methodName)] = true;
return $methodName;
}
private function getNextVariableName(): string
{
$firstChars = self::FIRST_CHARS;
$firstCharsLength = \strlen($firstChars);
$nonFirstChars = self::NON_FIRST_CHARS;
$nonFirstCharsLength = \strlen($nonFirstChars);
while (true) {
$name = '';
$i = $this->variableCount;
if ('' === $name) {
$name .= $firstChars[$i % $firstCharsLength];
$i = (int) ($i / $firstCharsLength);
}
while ($i > 0) {
--$i;
$name .= $nonFirstChars[$i % $nonFirstCharsLength];
$i = (int) ($i / $nonFirstCharsLength);
}
++$this->variableCount;
// check that the name is not reserved
if (\in_array($name, $this->reservedVariables, true)) {
continue;
}
return $name;
}
}
private function getExpressionLanguage(): ExpressionLanguage
{
if (null === $this->expressionLanguage) {
if (!class_exists('Symfony\Component\ExpressionLanguage\ExpressionLanguage')) {
throw new LogicException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed.');
}
$providers = $this->container->getExpressionLanguageProviders();
$this->expressionLanguage = new ExpressionLanguage(null, $providers, function ($arg) {
$id = '""' === substr_replace($arg, '', 1, -1) ? stripcslashes(substr($arg, 1, -1)) : null;
if (null !== $id && ($this->container->hasAlias($id) || $this->container->hasDefinition($id))) {
return $this->getServiceCall($id);
}
return sprintf('$this->get(%s)', $arg);
});
if ($this->container->isTrackingResources()) {
foreach ($providers as $provider) {
$this->container->addObjectResource($provider);
}
}
}
return $this->expressionLanguage;
}
private function isHotPath(Definition $definition): bool
{
return $this->hotPathTag && $definition->hasTag($this->hotPathTag) && !$definition->isDeprecated();
}
private function isSingleUsePrivateNode(ServiceReferenceGraphNode $node): bool
{
if ($node->getValue()->isPublic()) {
return false;
}
$ids = [];
foreach ($node->getInEdges() as $edge) {
if (!$value = $edge->getSourceNode()->getValue()) {
continue;
}
if ($edge->isLazy() || !$value instanceof Definition || !$value->isShared()) {
return false;
}
$ids[$edge->getSourceNode()->getId()] = true;
}
return 1 === \count($ids);
}
/**
* @return mixed
*/
private function export($value)
{
if (null !== $this->targetDirRegex && \is_string($value) && preg_match($this->targetDirRegex, $value, $matches, \PREG_OFFSET_CAPTURE)) {
$suffix = $matches[0][1] + \strlen($matches[0][0]);
$matches[0][1] += \strlen($matches[1][0]);
$prefix = $matches[0][1] ? $this->doExport(substr($value, 0, $matches[0][1]), true).'.' : '';
if ('\\' === \DIRECTORY_SEPARATOR && isset($value[$suffix])) {
$cookie = '\\'.random_int(100000, \PHP_INT_MAX);
$suffix = '.'.$this->doExport(str_replace('\\', $cookie, substr($value, $suffix)), true);
$suffix = str_replace('\\'.$cookie, "'.\\DIRECTORY_SEPARATOR.'", $suffix);
} else {
$suffix = isset($value[$suffix]) ? '.'.$this->doExport(substr($value, $suffix), true) : '';
}
$dirname = $this->asFiles ? '$this->containerDir' : '__DIR__';
$offset = 2 + $this->targetDirMaxMatches - \count($matches);
if (0 < $offset) {
$dirname = sprintf('\dirname(__DIR__, %d)', $offset + (int) $this->asFiles);
} elseif ($this->asFiles) {
$dirname = "\$this->targetDir.''"; // empty string concatenation on purpose
}
if ($prefix || $suffix) {
return sprintf('(%s%s%s)', $prefix, $dirname, $suffix);
}
return $dirname;
}
return $this->doExport($value, true);
}
/**
* @return mixed
*/
private function doExport($value, bool $resolveEnv = false)
{
$shouldCacheValue = $resolveEnv && \is_string($value);
if ($shouldCacheValue && isset($this->exportedVariables[$value])) {
return $this->exportedVariables[$value];
}
if (\is_string($value) && false !== strpos($value, "\n")) {
$cleanParts = explode("\n", $value);
$cleanParts = array_map(function ($part) { return var_export($part, true); }, $cleanParts);
$export = implode('."\n".', $cleanParts);
} else {
$export = var_export($value, true);
}
if ($this->asFiles) {
if (false !== strpos($export, '$this')) {
$export = str_replace('$this', "$'.'this", $export);
}
if (false !== strpos($export, 'function () {')) {
$export = str_replace('function () {', "function ('.') {", $export);
}
}
if ($resolveEnv && "'" === $export[0] && $export !== $resolvedExport = $this->container->resolveEnvPlaceholders($export, "'.\$this->getEnv('string:%s').'")) {
$export = $resolvedExport;
if (".''" === substr($export, -3)) {
$export = substr($export, 0, -3);
if ("'" === $export[1]) {
$export = substr_replace($export, '', 18, 7);
}
}
if ("'" === $export[1]) {
$export = substr($export, 3);
}
}
if ($shouldCacheValue) {
$this->exportedVariables[$value] = $export;
}
return $export;
}
private function getAutoloadFile(): ?string
{
$file = null;
foreach (spl_autoload_functions() as $autoloader) {
if (!\is_array($autoloader)) {
continue;
}
if ($autoloader[0] instanceof DebugClassLoader || $autoloader[0] instanceof LegacyDebugClassLoader) {
$autoloader = $autoloader[0]->getClassLoader();
}
if (!\is_array($autoloader) || !$autoloader[0] instanceof ClassLoader || !$autoloader[0]->findFile(__CLASS__)) {
continue;
}
foreach (get_declared_classes() as $class) {
if (0 === strpos($class, 'ComposerAutoloaderInit') && $class::getLoader() === $autoloader[0]) {
$file = \dirname((new \ReflectionClass($class))->getFileName(), 2).'/autoload.php';
if (null !== $this->targetDirRegex && preg_match($this->targetDirRegex.'A', $file)) {
return $file;
}
}
}
}
return $file;
}
private function getClasses(Definition $definition, string $id): array
{
$classes = [];
while ($definition instanceof Definition) {
foreach ($definition->getTag($this->preloadTags[0]) as $tag) {
if (!isset($tag['class'])) {
throw new InvalidArgumentException(sprintf('Missing attribute "class" on tag "%s" for service "%s".', $this->preloadTags[0], $id));
}
$classes[] = trim($tag['class'], '\\');
}
$classes[] = trim($definition->getClass(), '\\');
$factory = $definition->getFactory();
if (!\is_array($factory)) {
$factory = [$factory];
}
if (\is_string($factory[0])) {
if (false !== $i = strrpos($factory[0], '::')) {
$factory[0] = substr($factory[0], 0, $i);
}
$classes[] = trim($factory[0], '\\');
}
$definition = $factory[0];
}
return array_filter($classes);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Dumper;
use Symfony\Component\DependencyInjection\Alias;
use Symfony\Component\DependencyInjection\Argument\AbstractArgument;
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
use Symfony\Component\DependencyInjection\Parameter;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\ExpressionLanguage\Expression;
/**
* XmlDumper dumps a service container as an XML string.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Martin Hasoň <martin.hason@gmail.com>
*/
class XmlDumper extends Dumper
{
/**
* @var \DOMDocument
*/
private $document;
/**
* Dumps the service container as an XML string.
*
* @return string An xml string representing of the service container
*/
public function dump(array $options = [])
{
$this->document = new \DOMDocument('1.0', 'utf-8');
$this->document->formatOutput = true;
$container = $this->document->createElementNS('http://symfony.com/schema/dic/services', 'container');
$container->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
$container->setAttribute('xsi:schemaLocation', 'http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd');
$this->addParameters($container);
$this->addServices($container);
$this->document->appendChild($container);
$xml = $this->document->saveXML();
$this->document = null;
return $this->container->resolveEnvPlaceholders($xml);
}
private function addParameters(\DOMElement $parent)
{
$data = $this->container->getParameterBag()->all();
if (!$data) {
return;
}
if ($this->container->isCompiled()) {
$data = $this->escape($data);
}
$parameters = $this->document->createElement('parameters');
$parent->appendChild($parameters);
$this->convertParameters($data, 'parameter', $parameters);
}
private function addMethodCalls(array $methodcalls, \DOMElement $parent)
{
foreach ($methodcalls as $methodcall) {
$call = $this->document->createElement('call');
$call->setAttribute('method', $methodcall[0]);
if (\count($methodcall[1])) {
$this->convertParameters($methodcall[1], 'argument', $call);
}
if ($methodcall[2] ?? false) {
$call->setAttribute('returns-clone', 'true');
}
$parent->appendChild($call);
}
}
private function addService(Definition $definition, ?string $id, \DOMElement $parent)
{
$service = $this->document->createElement('service');
if (null !== $id) {
$service->setAttribute('id', $id);
}
if ($class = $definition->getClass()) {
if ('\\' === substr($class, 0, 1)) {
$class = substr($class, 1);
}
$service->setAttribute('class', $class);
}
if (!$definition->isShared()) {
$service->setAttribute('shared', 'false');
}
if ($definition->isPublic()) {
$service->setAttribute('public', 'true');
}
if ($definition->isSynthetic()) {
$service->setAttribute('synthetic', 'true');
}
if ($definition->isLazy()) {
$service->setAttribute('lazy', 'true');
}
if (null !== $decoratedService = $definition->getDecoratedService()) {
[$decorated, $renamedId, $priority] = $decoratedService;
$service->setAttribute('decorates', $decorated);
$decorationOnInvalid = $decoratedService[3] ?? ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE;
if (\in_array($decorationOnInvalid, [ContainerInterface::IGNORE_ON_INVALID_REFERENCE, ContainerInterface::NULL_ON_INVALID_REFERENCE], true)) {
$invalidBehavior = ContainerInterface::NULL_ON_INVALID_REFERENCE === $decorationOnInvalid ? 'null' : 'ignore';
$service->setAttribute('decoration-on-invalid', $invalidBehavior);
}
if (null !== $renamedId) {
$service->setAttribute('decoration-inner-name', $renamedId);
}
if (0 !== $priority) {
$service->setAttribute('decoration-priority', $priority);
}
}
foreach ($definition->getTags() as $name => $tags) {
foreach ($tags as $attributes) {
$tag = $this->document->createElement('tag');
if (!\array_key_exists('name', $attributes)) {
$tag->setAttribute('name', $name);
} else {
$tag->appendChild($this->document->createTextNode($name));
}
foreach ($attributes as $key => $value) {
$tag->setAttribute($key, $value);
}
$service->appendChild($tag);
}
}
if ($definition->getFile()) {
$file = $this->document->createElement('file');
$file->appendChild($this->document->createTextNode($definition->getFile()));
$service->appendChild($file);
}
if ($parameters = $definition->getArguments()) {
$this->convertParameters($parameters, 'argument', $service);
}
if ($parameters = $definition->getProperties()) {
$this->convertParameters($parameters, 'property', $service, 'name');
}
$this->addMethodCalls($definition->getMethodCalls(), $service);
if ($callable = $definition->getFactory()) {
$factory = $this->document->createElement('factory');
if (\is_array($callable) && $callable[0] instanceof Definition) {
$this->addService($callable[0], null, $factory);
$factory->setAttribute('method', $callable[1]);
} elseif (\is_array($callable)) {
if (null !== $callable[0]) {
$factory->setAttribute($callable[0] instanceof Reference ? 'service' : 'class', $callable[0]);
}
$factory->setAttribute('method', $callable[1]);
} else {
$factory->setAttribute('function', $callable);
}
$service->appendChild($factory);
}
if ($definition->isDeprecated()) {
$deprecation = $definition->getDeprecation('%service_id%');
$deprecated = $this->document->createElement('deprecated');
$deprecated->appendChild($this->document->createTextNode($definition->getDeprecation('%service_id%')['message']));
$deprecated->setAttribute('package', $deprecation['package']);
$deprecated->setAttribute('version', $deprecation['version']);
$service->appendChild($deprecated);
}
if ($definition->isAutowired()) {
$service->setAttribute('autowire', 'true');
}
if ($definition->isAutoconfigured()) {
$service->setAttribute('autoconfigure', 'true');
}
if ($definition->isAbstract()) {
$service->setAttribute('abstract', 'true');
}
if ($callable = $definition->getConfigurator()) {
$configurator = $this->document->createElement('configurator');
if (\is_array($callable) && $callable[0] instanceof Definition) {
$this->addService($callable[0], null, $configurator);
$configurator->setAttribute('method', $callable[1]);
} elseif (\is_array($callable)) {
$configurator->setAttribute($callable[0] instanceof Reference ? 'service' : 'class', $callable[0]);
$configurator->setAttribute('method', $callable[1]);
} else {
$configurator->setAttribute('function', $callable);
}
$service->appendChild($configurator);
}
$parent->appendChild($service);
}
private function addServiceAlias(string $alias, Alias $id, \DOMElement $parent)
{
$service = $this->document->createElement('service');
$service->setAttribute('id', $alias);
$service->setAttribute('alias', $id);
if ($id->isPublic()) {
$service->setAttribute('public', 'true');
}
if ($id->isDeprecated()) {
$deprecation = $id->getDeprecation('%alias_id%');
$deprecated = $this->document->createElement('deprecated');
$deprecated->appendChild($this->document->createTextNode($deprecation['message']));
$deprecated->setAttribute('package', $deprecation['package']);
$deprecated->setAttribute('version', $deprecation['version']);
$service->appendChild($deprecated);
}
$parent->appendChild($service);
}
private function addServices(\DOMElement $parent)
{
$definitions = $this->container->getDefinitions();
if (!$definitions) {
return;
}
$services = $this->document->createElement('services');
foreach ($definitions as $id => $definition) {
$this->addService($definition, $id, $services);
}
$aliases = $this->container->getAliases();
foreach ($aliases as $alias => $id) {
while (isset($aliases[(string) $id])) {
$id = $aliases[(string) $id];
}
$this->addServiceAlias($alias, $id, $services);
}
$parent->appendChild($services);
}
private function convertParameters(array $parameters, string $type, \DOMElement $parent, string $keyAttribute = 'key')
{
$withKeys = array_keys($parameters) !== range(0, \count($parameters) - 1);
foreach ($parameters as $key => $value) {
$element = $this->document->createElement($type);
if ($withKeys) {
$element->setAttribute($keyAttribute, $key);
}
if ($value instanceof ServiceClosureArgument) {
$value = $value->getValues()[0];
}
if (\is_array($tag = $value)) {
$element->setAttribute('type', 'collection');
$this->convertParameters($value, $type, $element, 'key');
} elseif ($value instanceof TaggedIteratorArgument || ($value instanceof ServiceLocatorArgument && $tag = $value->getTaggedIteratorArgument())) {
$element->setAttribute('type', $value instanceof TaggedIteratorArgument ? 'tagged_iterator' : 'tagged_locator');
$element->setAttribute('tag', $tag->getTag());
if (null !== $tag->getIndexAttribute()) {
$element->setAttribute('index-by', $tag->getIndexAttribute());
if (null !== $tag->getDefaultIndexMethod()) {
$element->setAttribute('default-index-method', $tag->getDefaultIndexMethod());
}
if (null !== $tag->getDefaultPriorityMethod()) {
$element->setAttribute('default-priority-method', $tag->getDefaultPriorityMethod());
}
}
} elseif ($value instanceof IteratorArgument) {
$element->setAttribute('type', 'iterator');
$this->convertParameters($value->getValues(), $type, $element, 'key');
} elseif ($value instanceof ServiceLocatorArgument) {
$element->setAttribute('type', 'service_locator');
$this->convertParameters($value->getValues(), $type, $element, 'key');
} elseif ($value instanceof Reference) {
$element->setAttribute('type', 'service');
$element->setAttribute('id', (string) $value);
$behavior = $value->getInvalidBehavior();
if (ContainerInterface::NULL_ON_INVALID_REFERENCE == $behavior) {
$element->setAttribute('on-invalid', 'null');
} elseif (ContainerInterface::IGNORE_ON_INVALID_REFERENCE == $behavior) {
$element->setAttribute('on-invalid', 'ignore');
} elseif (ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE == $behavior) {
$element->setAttribute('on-invalid', 'ignore_uninitialized');
}
} elseif ($value instanceof Definition) {
$element->setAttribute('type', 'service');
$this->addService($value, null, $element);
} elseif ($value instanceof Expression) {
$element->setAttribute('type', 'expression');
$text = $this->document->createTextNode(self::phpToXml((string) $value));
$element->appendChild($text);
} elseif (\is_string($value) && !preg_match('/^[^\x00-\x08\x0B\x0E-\x1A\x1C-\x1F\x7F]*+$/u', $value)) {
$element->setAttribute('type', 'binary');
$text = $this->document->createTextNode(self::phpToXml(base64_encode($value)));
$element->appendChild($text);
} elseif ($value instanceof AbstractArgument) {
$element->setAttribute('type', 'abstract');
$text = $this->document->createTextNode(self::phpToXml($value->getText()));
$element->appendChild($text);
} else {
if (\in_array($value, ['null', 'true', 'false'], true)) {
$element->setAttribute('type', 'string');
}
if (\is_string($value) && (is_numeric($value) || preg_match('/^0b[01]*$/', $value) || preg_match('/^0x[0-9a-f]++$/i', $value))) {
$element->setAttribute('type', 'string');
}
$text = $this->document->createTextNode(self::phpToXml($value));
$element->appendChild($text);
}
$parent->appendChild($element);
}
}
/**
* Escapes arguments.
*/
private function escape(array $arguments): array
{
$args = [];
foreach ($arguments as $k => $v) {
if (\is_array($v)) {
$args[$k] = $this->escape($v);
} elseif (\is_string($v)) {
$args[$k] = str_replace('%', '%%', $v);
} else {
$args[$k] = $v;
}
}
return $args;
}
/**
* Converts php types to xml types.
*
* @param mixed $value Value to convert
*
* @throws RuntimeException When trying to dump object or resource
*/
public static function phpToXml($value): string
{
switch (true) {
case null === $value:
return 'null';
case true === $value:
return 'true';
case false === $value:
return 'false';
case $value instanceof Parameter:
return '%'.$value.'%';
case \is_object($value) || \is_resource($value):
throw new RuntimeException('Unable to dump a service container if a parameter is an object or a resource.');
default:
return (string) $value;
}
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection;
/**
* ContainerAwareInterface should be implemented by classes that depends on a Container.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
interface ContainerAwareInterface
{
/**
* Sets the container.
*/
public function setContainer(ContainerInterface $container = null);
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
if (!function_exists('trigger_deprecation')) {
/**
* Triggers a silenced deprecation notice.
*
* @param string $package The name of the Composer package that is triggering the deprecation
* @param string $version The version of the package that introduced the deprecation
* @param string $message The message of the deprecation
* @param mixed ...$args Values to insert in the message using printf() formatting
*
* @author Nicolas Grekas <p@tchwork.com>
*/
function trigger_deprecation(string $package, string $version, string $message, ...$args): void
{
@trigger_error(($package || $version ? "Since $package $version: " : '').($args ? vsprintf($message, $args) : $message), \E_USER_DEPRECATED);
}
}
Copyright (c) 2020 Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
Copyright (c) 2004-2020 Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\EventDispatcher;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface as ContractsEventDispatcherInterface;
/**
* The EventDispatcherInterface is the central point of Symfony's event listener system.
* Listeners are registered on the manager and events are dispatched through the
* manager.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
interface EventDispatcherInterface extends ContractsEventDispatcherInterface
{
/**
* Adds an event listener that listens on the specified events.
*
* @param callable $listener The listener
* @param int $priority The higher this value, the earlier an event
* listener will be triggered in the chain (defaults to 0)
*/
public function addListener(string $eventName, $listener, int $priority = 0);
/**
* Adds an event subscriber.
*
* The subscriber is asked for all the events it is
* interested in and added as a listener for these events.
*/
public function addSubscriber(EventSubscriberInterface $subscriber);
/**
* Removes an event listener from the specified events.
*
* @param callable $listener The listener to remove
*/
public function removeListener(string $eventName, $listener);
public function removeSubscriber(EventSubscriberInterface $subscriber);
/**
* Gets the listeners of a specific event or all listeners sorted by descending priority.
*
* @return array The event listeners for the specified event, or all event listeners by event name
*/
public function getListeners(string $eventName = null);
/**
* Gets the listener priority for a specific event.
*
* Returns null if the event or the listener does not exist.
*
* @param callable $listener The listener
*
* @return int|null The event listener priority
*/
public function getListenerPriority(string $eventName, $listener);
/**
* Checks whether an event has any registered listeners.
*
* @return bool true if the specified event has any listeners, false otherwise
*/
public function hasListeners(string $eventName = null);
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\EventDispatcher;
use Psr\EventDispatcher\StoppableEventInterface;
use Symfony\Component\EventDispatcher\Debug\WrappedListener;
/**
* The EventDispatcherInterface is the central point of Symfony's event listener system.
*
* Listeners are registered on the manager and events are dispatched through the
* manager.
*
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
* @author Bernhard Schussek <bschussek@gmail.com>
* @author Fabien Potencier <fabien@symfony.com>
* @author Jordi Boggiano <j.boggiano@seld.be>
* @author Jordan Alliot <jordan.alliot@gmail.com>
* @author Nicolas Grekas <p@tchwork.com>
*/
class EventDispatcher implements EventDispatcherInterface
{
private $listeners = [];
private $sorted = [];
private $optimized;
public function __construct()
{
if (__CLASS__ === static::class) {
$this->optimized = [];
}
}
/**
* {@inheritdoc}
*/
public function dispatch(object $event, string $eventName = null): object
{
$eventName = $eventName ?? \get_class($event);
if (null !== $this->optimized) {
$listeners = $this->optimized[$eventName] ?? (empty($this->listeners[$eventName]) ? [] : $this->optimizeListeners($eventName));
} else {
$listeners = $this->getListeners($eventName);
}
if ($listeners) {
$this->callListeners($listeners, $eventName, $event);
}
return $event;
}
/**
* {@inheritdoc}
*/
public function getListeners(string $eventName = null)
{
if (null !== $eventName) {
if (empty($this->listeners[$eventName])) {
return [];
}
if (!isset($this->sorted[$eventName])) {
$this->sortListeners($eventName);
}
return $this->sorted[$eventName];
}
foreach ($this->listeners as $eventName => $eventListeners) {
if (!isset($this->sorted[$eventName])) {
$this->sortListeners($eventName);
}
}
return array_filter($this->sorted);
}
/**
* {@inheritdoc}
*/
public function getListenerPriority(string $eventName, $listener)
{
if (empty($this->listeners[$eventName])) {
return null;
}
if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure && 2 >= \count($listener)) {
$listener[0] = $listener[0]();
$listener[1] = $listener[1] ?? '__invoke';
}
foreach ($this->listeners[$eventName] as $priority => &$listeners) {
foreach ($listeners as &$v) {
if ($v !== $listener && \is_array($v) && isset($v[0]) && $v[0] instanceof \Closure && 2 >= \count($v)) {
$v[0] = $v[0]();
$v[1] = $v[1] ?? '__invoke';
}
if ($v === $listener) {
return $priority;
}
}
}
return null;
}
/**
* {@inheritdoc}
*/
public function hasListeners(string $eventName = null)
{
if (null !== $eventName) {
return !empty($this->listeners[$eventName]);
}
foreach ($this->listeners as $eventListeners) {
if ($eventListeners) {
return true;
}
}
return false;
}
/**
* {@inheritdoc}
*/
public function addListener(string $eventName, $listener, int $priority = 0)
{
$this->listeners[$eventName][$priority][] = $listener;
unset($this->sorted[$eventName], $this->optimized[$eventName]);
}
/**
* {@inheritdoc}
*/
public function removeListener(string $eventName, $listener)
{
if (empty($this->listeners[$eventName])) {
return;
}
if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure && 2 >= \count($listener)) {
$listener[0] = $listener[0]();
$listener[1] = $listener[1] ?? '__invoke';
}
foreach ($this->listeners[$eventName] as $priority => &$listeners) {
foreach ($listeners as $k => &$v) {
if ($v !== $listener && \is_array($v) && isset($v[0]) && $v[0] instanceof \Closure && 2 >= \count($v)) {
$v[0] = $v[0]();
$v[1] = $v[1] ?? '__invoke';
}
if ($v === $listener) {
unset($listeners[$k], $this->sorted[$eventName], $this->optimized[$eventName]);
}
}
if (!$listeners) {
unset($this->listeners[$eventName][$priority]);
}
}
}
/**
* {@inheritdoc}
*/
public function addSubscriber(EventSubscriberInterface $subscriber)
{
foreach ($subscriber->getSubscribedEvents() as $eventName => $params) {
if (\is_string($params)) {
$this->addListener($eventName, [$subscriber, $params]);
} elseif (\is_string($params[0])) {
$this->addListener($eventName, [$subscriber, $params[0]], isset($params[1]) ? $params[1] : 0);
} else {
foreach ($params as $listener) {
$this->addListener($eventName, [$subscriber, $listener[0]], isset($listener[1]) ? $listener[1] : 0);
}
}
}
}
/**
* {@inheritdoc}
*/
public function removeSubscriber(EventSubscriberInterface $subscriber)
{
foreach ($subscriber->getSubscribedEvents() as $eventName => $params) {
if (\is_array($params) && \is_array($params[0])) {
foreach ($params as $listener) {
$this->removeListener($eventName, [$subscriber, $listener[0]]);
}
} else {
$this->removeListener($eventName, [$subscriber, \is_string($params) ? $params : $params[0]]);
}
}
}
/**
* Triggers the listeners of an event.
*
* This method can be overridden to add functionality that is executed
* for each listener.
*
* @param callable[] $listeners The event listeners
* @param string $eventName The name of the event to dispatch
* @param object $event The event object to pass to the event handlers/listeners
*/
protected function callListeners(iterable $listeners, string $eventName, object $event)
{
$stoppable = $event instanceof StoppableEventInterface;
foreach ($listeners as $listener) {
if ($stoppable && $event->isPropagationStopped()) {
break;
}
$listener($event, $eventName, $this);
}
}
/**
* Sorts the internal list of listeners for the given event by priority.
*/
private function sortListeners(string $eventName)
{
krsort($this->listeners[$eventName]);
$this->sorted[$eventName] = [];
foreach ($this->listeners[$eventName] as &$listeners) {
foreach ($listeners as $k => &$listener) {
if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure && 2 >= \count($listener)) {
$listener[0] = $listener[0]();
$listener[1] = $listener[1] ?? '__invoke';
}
$this->sorted[$eventName][] = $listener;
}
}
}
/**
* Optimizes the internal list of listeners for the given event by priority.
*/
private function optimizeListeners(string $eventName): array
{
krsort($this->listeners[$eventName]);
$this->optimized[$eventName] = [];
foreach ($this->listeners[$eventName] as &$listeners) {
foreach ($listeners as &$listener) {
$closure = &$this->optimized[$eventName][];
if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure && 2 >= \count($listener)) {
$closure = static function (...$args) use (&$listener, &$closure) {
if ($listener[0] instanceof \Closure) {
$listener[0] = $listener[0]();
$listener[1] = $listener[1] ?? '__invoke';
}
($closure = \Closure::fromCallable($listener))(...$args);
};
} else {
$closure = $listener instanceof \Closure || $listener instanceof WrappedListener ? $listener : \Closure::fromCallable($listener);
}
}
}
return $this->optimized[$eventName];
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\EventDispatcher;
use Symfony\Contracts\EventDispatcher\Event;
/**
* Event encapsulation class.
*
* Encapsulates events thus decoupling the observer from the subject they encapsulate.
*
* @author Drak <drak@zikula.org>
*/
class GenericEvent extends Event implements \ArrayAccess, \IteratorAggregate
{
protected $subject;
protected $arguments;
/**
* Encapsulate an event with $subject and $args.
*
* @param mixed $subject The subject of the event, usually an object or a callable
* @param array $arguments Arguments to store in the event
*/
public function __construct($subject = null, array $arguments = [])
{
$this->subject = $subject;
$this->arguments = $arguments;
}
/**
* Getter for subject property.
*
* @return mixed The observer subject
*/
public function getSubject()
{
return $this->subject;
}
/**
* Get argument by key.
*
* @return mixed Contents of array key
*
* @throws \InvalidArgumentException if key is not found
*/
public function getArgument(string $key)
{
if ($this->hasArgument($key)) {
return $this->arguments[$key];
}
throw new \InvalidArgumentException(sprintf('Argument "%s" not found.', $key));
}
/**
* Add argument to event.
*
* @param mixed $value Value
*
* @return $this
*/
public function setArgument(string $key, $value)
{
$this->arguments[$key] = $value;
return $this;
}
/**
* Getter for all arguments.
*
* @return array
*/
public function getArguments()
{
return $this->arguments;
}
/**
* Set args property.
*
* @return $this
*/
public function setArguments(array $args = [])
{
$this->arguments = $args;
return $this;
}
/**
* Has argument.
*
* @return bool
*/
public function hasArgument(string $key)
{
return \array_key_exists($key, $this->arguments);
}
/**
* ArrayAccess for argument getter.
*
* @param string $key Array key
*
* @return mixed
*
* @throws \InvalidArgumentException if key does not exist in $this->args
*/
public function offsetGet($key)
{
return $this->getArgument($key);
}
/**
* ArrayAccess for argument setter.
*
* @param string $key Array key to set
* @param mixed $value Value
*/
public function offsetSet($key, $value)
{
$this->setArgument($key, $value);
}
/**
* ArrayAccess for unset argument.
*
* @param string $key Array key
*/
public function offsetUnset($key)
{
if ($this->hasArgument($key)) {
unset($this->arguments[$key]);
}
}
/**
* ArrayAccess has argument.
*
* @param string $key Array key
*
* @return bool
*/
public function offsetExists($key)
{
return $this->hasArgument($key);
}
/**
* IteratorAggregate for iterating over the object like an array.
*
* @return \ArrayIterator
*/
public function getIterator()
{
return new \ArrayIterator($this->arguments);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\EventDispatcher;
/**
* A read-only proxy for an event dispatcher.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class ImmutableEventDispatcher implements EventDispatcherInterface
{
private $dispatcher;
public function __construct(EventDispatcherInterface $dispatcher)
{
$this->dispatcher = $dispatcher;
}
/**
* {@inheritdoc}
*/
public function dispatch(object $event, string $eventName = null): object
{
return $this->dispatcher->dispatch($event, $eventName);
}
/**
* {@inheritdoc}
*/
public function addListener(string $eventName, $listener, int $priority = 0)
{
throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.');
}
/**
* {@inheritdoc}
*/
public function addSubscriber(EventSubscriberInterface $subscriber)
{
throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.');
}
/**
* {@inheritdoc}
*/
public function removeListener(string $eventName, $listener)
{
throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.');
}
/**
* {@inheritdoc}
*/
public function removeSubscriber(EventSubscriberInterface $subscriber)
{
throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.');
}
/**
* {@inheritdoc}
*/
public function getListeners(string $eventName = null)
{
return $this->dispatcher->getListeners($eventName);
}
/**
* {@inheritdoc}
*/
public function getListenerPriority(string $eventName, $listener)
{
return $this->dispatcher->getListenerPriority($eventName, $listener);
}
/**
* {@inheritdoc}
*/
public function hasListeners(string $eventName = null)
{
return $this->dispatcher->hasListeners($eventName);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\EventDispatcher;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
trigger_deprecation('symfony/event-dispatcher', '5.1', '%s is deprecated, use the event dispatcher without the proxy.', LegacyEventDispatcherProxy::class);
/**
* A helper class to provide BC/FC with the legacy signature of EventDispatcherInterface::dispatch().
*
* @author Nicolas Grekas <p@tchwork.com>
*
* @deprecated since Symfony 5.1
*/
final class LegacyEventDispatcherProxy
{
public static function decorate(?EventDispatcherInterface $dispatcher): ?EventDispatcherInterface
{
return $dispatcher;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\EventDispatcher;
/**
* An EventSubscriber knows itself what events it is interested in.
* If an EventSubscriber is added to an EventDispatcherInterface, the manager invokes
* {@link getSubscribedEvents} and registers the subscriber as a listener for all
* returned events.
*
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
* @author Bernhard Schussek <bschussek@gmail.com>
*/
interface EventSubscriberInterface
{
/**
* Returns an array of event names this subscriber wants to listen to.
*
* The array keys are event names and the value can be:
*
* * The method name to call (priority defaults to 0)
* * An array composed of the method name to call and the priority
* * An array of arrays composed of the method names to call and respective
* priorities, or 0 if unset
*
* For instance:
*
* * ['eventName' => 'methodName']
* * ['eventName' => ['methodName', $priority]]
* * ['eventName' => [['methodName1', $priority], ['methodName2']]]
*
* The code must not depend on runtime state as it will only be called at compile time.
* All logic depending on runtime state must be put into the individual methods handling the events.
*
* @return array The event names to listen to
*/
public static function getSubscribedEvents();
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\EventDispatcher\Debug;
use Psr\EventDispatcher\StoppableEventInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Stopwatch\Stopwatch;
use Symfony\Component\VarDumper\Caster\ClassStub;
/**
* @author Fabien Potencier <fabien@symfony.com>
*/
final class WrappedListener
{
private $listener;
private $optimizedListener;
private $name;
private $called;
private $stoppedPropagation;
private $stopwatch;
private $dispatcher;
private $pretty;
private $stub;
private $priority;
private static $hasClassStub;
public function __construct($listener, ?string $name, Stopwatch $stopwatch, EventDispatcherInterface $dispatcher = null)
{
$this->listener = $listener;
$this->optimizedListener = $listener instanceof \Closure ? $listener : (\is_callable($listener) ? \Closure::fromCallable($listener) : null);
$this->stopwatch = $stopwatch;
$this->dispatcher = $dispatcher;
$this->called = false;
$this->stoppedPropagation = false;
if (\is_array($listener)) {
$this->name = \is_object($listener[0]) ? get_debug_type($listener[0]) : $listener[0];
$this->pretty = $this->name.'::'.$listener[1];
} elseif ($listener instanceof \Closure) {
$r = new \ReflectionFunction($listener);
if (false !== strpos($r->name, '{closure}')) {
$this->pretty = $this->name = 'closure';
} elseif ($class = $r->getClosureScopeClass()) {
$this->name = $class->name;
$this->pretty = $this->name.'::'.$r->name;
} else {
$this->pretty = $this->name = $r->name;
}
} elseif (\is_string($listener)) {
$this->pretty = $this->name = $listener;
} else {
$this->name = get_debug_type($listener);
$this->pretty = $this->name.'::__invoke';
}
if (null !== $name) {
$this->name = $name;
}
if (null === self::$hasClassStub) {
self::$hasClassStub = class_exists(ClassStub::class);
}
}
public function getWrappedListener()
{
return $this->listener;
}
public function wasCalled(): bool
{
return $this->called;
}
public function stoppedPropagation(): bool
{
return $this->stoppedPropagation;
}
public function getPretty(): string
{
return $this->pretty;
}
public function getInfo(string $eventName): array
{
if (null === $this->stub) {
$this->stub = self::$hasClassStub ? new ClassStub($this->pretty.'()', $this->listener) : $this->pretty.'()';
}
return [
'event' => $eventName,
'priority' => null !== $this->priority ? $this->priority : (null !== $this->dispatcher ? $this->dispatcher->getListenerPriority($eventName, $this->listener) : null),
'pretty' => $this->pretty,
'stub' => $this->stub,
];
}
public function __invoke(object $event, string $eventName, EventDispatcherInterface $dispatcher): void
{
$dispatcher = $this->dispatcher ?: $dispatcher;
$this->called = true;
$this->priority = $dispatcher->getListenerPriority($eventName, $this->listener);
$e = $this->stopwatch->start($this->name, 'event_listener');
($this->optimizedListener ?? $this->listener)($event, $eventName, $dispatcher);
if ($e->isStarted()) {
$e->stop();
}
if ($event instanceof StoppableEventInterface && $event->isPropagationStopped()) {
$this->stoppedPropagation = true;
}
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\EventDispatcher\Debug;
use Psr\EventDispatcher\StoppableEventInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Stopwatch\Stopwatch;
use Symfony\Contracts\Service\ResetInterface;
/**
* Collects some data about event listeners.
*
* This event dispatcher delegates the dispatching to another one.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class TraceableEventDispatcher implements EventDispatcherInterface, ResetInterface
{
protected $logger;
protected $stopwatch;
private $callStack;
private $dispatcher;
private $wrappedListeners;
private $orphanedEvents;
private $requestStack;
private $currentRequestHash = '';
public function __construct(EventDispatcherInterface $dispatcher, Stopwatch $stopwatch, LoggerInterface $logger = null, RequestStack $requestStack = null)
{
$this->dispatcher = $dispatcher;
$this->stopwatch = $stopwatch;
$this->logger = $logger;
$this->wrappedListeners = [];
$this->orphanedEvents = [];
$this->requestStack = $requestStack;
}
/**
* {@inheritdoc}
*/
public function addListener(string $eventName, $listener, int $priority = 0)
{
$this->dispatcher->addListener($eventName, $listener, $priority);
}
/**
* {@inheritdoc}
*/
public function addSubscriber(EventSubscriberInterface $subscriber)
{
$this->dispatcher->addSubscriber($subscriber);
}
/**
* {@inheritdoc}
*/
public function removeListener(string $eventName, $listener)
{
if (isset($this->wrappedListeners[$eventName])) {
foreach ($this->wrappedListeners[$eventName] as $index => $wrappedListener) {
if ($wrappedListener->getWrappedListener() === $listener) {
$listener = $wrappedListener;
unset($this->wrappedListeners[$eventName][$index]);
break;
}
}
}
return $this->dispatcher->removeListener($eventName, $listener);
}
/**
* {@inheritdoc}
*/
public function removeSubscriber(EventSubscriberInterface $subscriber)
{
return $this->dispatcher->removeSubscriber($subscriber);
}
/**
* {@inheritdoc}
*/
public function getListeners(string $eventName = null)
{
return $this->dispatcher->getListeners($eventName);
}
/**
* {@inheritdoc}
*/
public function getListenerPriority(string $eventName, $listener)
{
// we might have wrapped listeners for the event (if called while dispatching)
// in that case get the priority by wrapper
if (isset($this->wrappedListeners[$eventName])) {
foreach ($this->wrappedListeners[$eventName] as $index => $wrappedListener) {
if ($wrappedListener->getWrappedListener() === $listener) {
return $this->dispatcher->getListenerPriority($eventName, $wrappedListener);
}
}
}
return $this->dispatcher->getListenerPriority($eventName, $listener);
}
/**
* {@inheritdoc}
*/
public function hasListeners(string $eventName = null)
{
return $this->dispatcher->hasListeners($eventName);
}
/**
* {@inheritdoc}
*/
public function dispatch(object $event, string $eventName = null): object
{
$eventName = $eventName ?? \get_class($event);
if (null === $this->callStack) {
$this->callStack = new \SplObjectStorage();
}
$currentRequestHash = $this->currentRequestHash = $this->requestStack && ($request = $this->requestStack->getCurrentRequest()) ? spl_object_hash($request) : '';
if (null !== $this->logger && $event instanceof StoppableEventInterface && $event->isPropagationStopped()) {
$this->logger->debug(sprintf('The "%s" event is already stopped. No listeners have been called.', $eventName));
}
$this->preProcess($eventName);
try {
$this->beforeDispatch($eventName, $event);
try {
$e = $this->stopwatch->start($eventName, 'section');
try {
$this->dispatcher->dispatch($event, $eventName);
} finally {
if ($e->isStarted()) {
$e->stop();
}
}
} finally {
$this->afterDispatch($eventName, $event);
}
} finally {
$this->currentRequestHash = $currentRequestHash;
$this->postProcess($eventName);
}
return $event;
}
/**
* @return array
*/
public function getCalledListeners(Request $request = null)
{
if (null === $this->callStack) {
return [];
}
$hash = $request ? spl_object_hash($request) : null;
$called = [];
foreach ($this->callStack as $listener) {
[$eventName, $requestHash] = $this->callStack->getInfo();
if (null === $hash || $hash === $requestHash) {
$called[] = $listener->getInfo($eventName);
}
}
return $called;
}
/**
* @return array
*/
public function getNotCalledListeners(Request $request = null)
{
try {
$allListeners = $this->getListeners();
} catch (\Exception $e) {
if (null !== $this->logger) {
$this->logger->info('An exception was thrown while getting the uncalled listeners.', ['exception' => $e]);
}
// unable to retrieve the uncalled listeners
return [];
}
$hash = $request ? spl_object_hash($request) : null;
$calledListeners = [];
if (null !== $this->callStack) {
foreach ($this->callStack as $calledListener) {
[, $requestHash] = $this->callStack->getInfo();
if (null === $hash || $hash === $requestHash) {
$calledListeners[] = $calledListener->getWrappedListener();
}
}
}
$notCalled = [];
foreach ($allListeners as $eventName => $listeners) {
foreach ($listeners as $listener) {
if (!\in_array($listener, $calledListeners, true)) {
if (!$listener instanceof WrappedListener) {
$listener = new WrappedListener($listener, null, $this->stopwatch, $this);
}
$notCalled[] = $listener->getInfo($eventName);
}
}
}
uasort($notCalled, [$this, 'sortNotCalledListeners']);
return $notCalled;
}
public function getOrphanedEvents(Request $request = null): array
{
if ($request) {
return $this->orphanedEvents[spl_object_hash($request)] ?? [];
}
if (!$this->orphanedEvents) {
return [];
}
return array_merge(...array_values($this->orphanedEvents));
}
public function reset()
{
$this->callStack = null;
$this->orphanedEvents = [];
$this->currentRequestHash = '';
}
/**
* Proxies all method calls to the original event dispatcher.
*
* @param string $method The method name
* @param array $arguments The method arguments
*
* @return mixed
*/
public function __call(string $method, array $arguments)
{
return $this->dispatcher->{$method}(...$arguments);
}
/**
* Called before dispatching the event.
*/
protected function beforeDispatch(string $eventName, object $event)
{
}
/**
* Called after dispatching the event.
*/
protected function afterDispatch(string $eventName, object $event)
{
}
private function preProcess(string $eventName): void
{
if (!$this->dispatcher->hasListeners($eventName)) {
$this->orphanedEvents[$this->currentRequestHash][] = $eventName;
return;
}
foreach ($this->dispatcher->getListeners($eventName) as $listener) {
$priority = $this->getListenerPriority($eventName, $listener);
$wrappedListener = new WrappedListener($listener instanceof WrappedListener ? $listener->getWrappedListener() : $listener, null, $this->stopwatch, $this);
$this->wrappedListeners[$eventName][] = $wrappedListener;
$this->dispatcher->removeListener($eventName, $listener);
$this->dispatcher->addListener($eventName, $wrappedListener, $priority);
$this->callStack->attach($wrappedListener, [$eventName, $this->currentRequestHash]);
}
}
private function postProcess(string $eventName): void
{
unset($this->wrappedListeners[$eventName]);
$skipped = false;
foreach ($this->dispatcher->getListeners($eventName) as $listener) {
if (!$listener instanceof WrappedListener) { // #12845: a new listener was added during dispatch.
continue;
}
// Unwrap listener
$priority = $this->getListenerPriority($eventName, $listener);
$this->dispatcher->removeListener($eventName, $listener);
$this->dispatcher->addListener($eventName, $listener->getWrappedListener(), $priority);
if (null !== $this->logger) {
$context = ['event' => $eventName, 'listener' => $listener->getPretty()];
}
if ($listener->wasCalled()) {
if (null !== $this->logger) {
$this->logger->debug('Notified event "{event}" to listener "{listener}".', $context);
}
} else {
$this->callStack->detach($listener);
}
if (null !== $this->logger && $skipped) {
$this->logger->debug('Listener "{listener}" was not called for event "{event}".', $context);
}
if ($listener->stoppedPropagation()) {
if (null !== $this->logger) {
$this->logger->debug('Listener "{listener}" stopped propagation of the event "{event}".', $context);
}
$skipped = true;
}
}
}
private function sortNotCalledListeners(array $a, array $b)
{
if (0 !== $cmp = strcmp($a['event'], $b['event'])) {
return $cmp;
}
if (\is_int($a['priority']) && !\is_int($b['priority'])) {
return 1;
}
if (!\is_int($a['priority']) && \is_int($b['priority'])) {
return -1;
}
if ($a['priority'] === $b['priority']) {
return 0;
}
if ($a['priority'] > $b['priority']) {
return -1;
}
return 1;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\EventDispatcher\DependencyInjection;
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Contracts\EventDispatcher\Event;
/**
* Compiler pass to register tagged services for an event dispatcher.
*/
class RegisterListenersPass implements CompilerPassInterface
{
protected $dispatcherService;
protected $listenerTag;
protected $subscriberTag;
protected $eventAliasesParameter;
private $hotPathEvents = [];
private $hotPathTagName;
private $noPreloadEvents = [];
private $noPreloadTagName;
public function __construct(string $dispatcherService = 'event_dispatcher', string $listenerTag = 'kernel.event_listener', string $subscriberTag = 'kernel.event_subscriber', string $eventAliasesParameter = 'event_dispatcher.event_aliases')
{
$this->dispatcherService = $dispatcherService;
$this->listenerTag = $listenerTag;
$this->subscriberTag = $subscriberTag;
$this->eventAliasesParameter = $eventAliasesParameter;
}
/**
* @return $this
*/
public function setHotPathEvents(array $hotPathEvents, string $tagName = 'container.hot_path')
{
$this->hotPathEvents = array_flip($hotPathEvents);
$this->hotPathTagName = $tagName;
return $this;
}
/**
* @return $this
*/
public function setNoPreloadEvents(array $noPreloadEvents, string $tagName = 'container.no_preload'): self
{
$this->noPreloadEvents = array_flip($noPreloadEvents);
$this->noPreloadTagName = $tagName;
return $this;
}
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition($this->dispatcherService) && !$container->hasAlias($this->dispatcherService)) {
return;
}
$aliases = [];
if ($container->hasParameter($this->eventAliasesParameter)) {
$aliases = $container->getParameter($this->eventAliasesParameter);
}
$globalDispatcherDefinition = $container->findDefinition($this->dispatcherService);
foreach ($container->findTaggedServiceIds($this->listenerTag, true) as $id => $events) {
$noPreload = 0;
foreach ($events as $event) {
$priority = isset($event['priority']) ? $event['priority'] : 0;
if (!isset($event['event'])) {
if ($container->getDefinition($id)->hasTag($this->subscriberTag)) {
continue;
}
$event['method'] = $event['method'] ?? '__invoke';
$event['event'] = $this->getEventFromTypeDeclaration($container, $id, $event['method']);
}
$event['event'] = $aliases[$event['event']] ?? $event['event'];
if (!isset($event['method'])) {
$event['method'] = 'on'.preg_replace_callback([
'/(?<=\b)[a-z]/i',
'/[^a-z0-9]/i',
], function ($matches) { return strtoupper($matches[0]); }, $event['event']);
$event['method'] = preg_replace('/[^a-z0-9]/i', '', $event['method']);
if (null !== ($class = $container->getDefinition($id)->getClass()) && ($r = $container->getReflectionClass($class, false)) && !$r->hasMethod($event['method']) && $r->hasMethod('__invoke')) {
$event['method'] = '__invoke';
}
}
$dispatcherDefinition = $globalDispatcherDefinition;
if (isset($event['dispatcher'])) {
$dispatcherDefinition = $container->getDefinition($event['dispatcher']);
}
$dispatcherDefinition->addMethodCall('addListener', [$event['event'], [new ServiceClosureArgument(new Reference($id)), $event['method']], $priority]);
if (isset($this->hotPathEvents[$event['event']])) {
$container->getDefinition($id)->addTag($this->hotPathTagName);
} elseif (isset($this->noPreloadEvents[$event['event']])) {
++$noPreload;
}
}
if ($noPreload && \count($events) === $noPreload) {
$container->getDefinition($id)->addTag($this->noPreloadTagName);
}
}
$extractingDispatcher = new ExtractingEventDispatcher();
foreach ($container->findTaggedServiceIds($this->subscriberTag, true) as $id => $tags) {
$def = $container->getDefinition($id);
// We must assume that the class value has been correctly filled, even if the service is created by a factory
$class = $def->getClass();
if (!$r = $container->getReflectionClass($class)) {
throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id));
}
if (!$r->isSubclassOf(EventSubscriberInterface::class)) {
throw new InvalidArgumentException(sprintf('Service "%s" must implement interface "%s".', $id, EventSubscriberInterface::class));
}
$class = $r->name;
$dispatcherDefinitions = [];
foreach ($tags as $attributes) {
if (!isset($attributes['dispatcher']) || isset($dispatcherDefinitions[$attributes['dispatcher']])) {
continue;
}
$dispatcherDefinitions[] = $container->getDefinition($attributes['dispatcher']);
}
if (!$dispatcherDefinitions) {
$dispatcherDefinitions = [$globalDispatcherDefinition];
}
$noPreload = 0;
ExtractingEventDispatcher::$aliases = $aliases;
ExtractingEventDispatcher::$subscriber = $class;
$extractingDispatcher->addSubscriber($extractingDispatcher);
foreach ($extractingDispatcher->listeners as $args) {
$args[1] = [new ServiceClosureArgument(new Reference($id)), $args[1]];
foreach ($dispatcherDefinitions as $dispatcherDefinition) {
$dispatcherDefinition->addMethodCall('addListener', $args);
}
if (isset($this->hotPathEvents[$args[0]])) {
$container->getDefinition($id)->addTag($this->hotPathTagName);
} elseif (isset($this->noPreloadEvents[$args[0]])) {
++$noPreload;
}
}
if ($noPreload && \count($extractingDispatcher->listeners) === $noPreload) {
$container->getDefinition($id)->addTag($this->noPreloadTagName);
}
$extractingDispatcher->listeners = [];
ExtractingEventDispatcher::$aliases = [];
}
}
private function getEventFromTypeDeclaration(ContainerBuilder $container, string $id, string $method): string
{
if (
null === ($class = $container->getDefinition($id)->getClass())
|| !($r = $container->getReflectionClass($class, false))
|| !$r->hasMethod($method)
|| 1 > ($m = $r->getMethod($method))->getNumberOfParameters()
|| !($type = $m->getParameters()[0]->getType()) instanceof \ReflectionNamedType
|| $type->isBuiltin()
|| Event::class === ($name = $type->getName())
) {
throw new InvalidArgumentException(sprintf('Service "%s" must define the "event" attribute on "%s" tags.', $id, $this->listenerTag));
}
return $name;
}
}
/**
* @internal
*/
class ExtractingEventDispatcher extends EventDispatcher implements EventSubscriberInterface
{
public $listeners = [];
public static $aliases = [];
public static $subscriber;
public function addListener(string $eventName, $listener, int $priority = 0)
{
$this->listeners[] = [$eventName, $listener[1], $priority];
}
public static function getSubscribedEvents(): array
{
$events = [];
foreach ([self::$subscriber, 'getSubscribedEvents']() as $eventName => $params) {
$events[self::$aliases[$eventName] ?? $eventName] = $params;
}
return $events;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\EventDispatcher\DependencyInjection;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
/**
* This pass allows bundles to extend the list of event aliases.
*
* @author Alexander M. Turek <me@derrabus.de>
*/
class AddEventAliasesPass implements CompilerPassInterface
{
private $eventAliases;
private $eventAliasesParameter;
public function __construct(array $eventAliases, string $eventAliasesParameter = 'event_dispatcher.event_aliases')
{
$this->eventAliases = $eventAliases;
$this->eventAliasesParameter = $eventAliasesParameter;
}
public function process(ContainerBuilder $container): void
{
$eventAliases = $container->hasParameter($this->eventAliasesParameter) ? $container->getParameter($this->eventAliasesParameter) : [];
$container->setParameter(
$this->eventAliasesParameter,
array_merge($eventAliases, $this->eventAliases)
);
}
}
Copyright (c) 2018-2020 Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Contracts\EventDispatcher;
use Psr\EventDispatcher\EventDispatcherInterface as PsrEventDispatcherInterface;
/**
* Allows providing hooks on domain-specific lifecycles by dispatching events.
*/
interface EventDispatcherInterface extends PsrEventDispatcherInterface
{
/**
* Dispatches an event to all registered listeners.
*
* @param object $event The event to pass to the event handlers/listeners
* @param string|null $eventName The name of the event to dispatch. If not supplied,
* the class of $event should be used instead.
*
* @return object The passed $event MUST be returned
*/
public function dispatch(object $event, string $eventName = null): object;
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Contracts\EventDispatcher;
use Psr\EventDispatcher\StoppableEventInterface;
/**
* Event is the base class for classes containing event data.
*
* This class contains no event data. It is used by events that do not pass
* state information to an event handler when an event is raised.
*
* You can call the method stopPropagation() to abort the execution of
* further listeners in your event listener.
*
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
* @author Bernhard Schussek <bschussek@gmail.com>
* @author Nicolas Grekas <p@tchwork.com>
*/
class Event implements StoppableEventInterface
{
private $propagationStopped = false;
/**
* {@inheritdoc}
*/
public function isPropagationStopped(): bool
{
return $this->propagationStopped;
}
/**
* Stops the propagation of the event to further event listeners.
*
* If multiple event listeners are connected to the same event, no
* further event listener will be triggered once any trigger calls
* stopPropagation().
*/
public function stopPropagation(): void
{
$this->propagationStopped = true;
}
}
Copyright (c) 2004-2020 Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Filesystem\Exception;
/**
* Exception interface for all exceptions thrown by the component.
*
* @author Romain Neutron <imprec@gmail.com>
*/
interface ExceptionInterface extends \Throwable
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Filesystem\Exception;
/**
* IOException interface for file and input/output stream related exceptions thrown by the component.
*
* @author Christian Gärtner <christiangaertner.film@googlemail.com>
*/
interface IOExceptionInterface extends ExceptionInterface
{
/**
* Returns the associated path for the exception.
*
* @return string|null The path
*/
public function getPath();
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Filesystem\Exception;
/**
* Exception class thrown when a filesystem operation failure happens.
*
* @author Romain Neutron <imprec@gmail.com>
* @author Christian Gärtner <christiangaertner.film@googlemail.com>
* @author Fabien Potencier <fabien@symfony.com>
*/
class IOException extends \RuntimeException implements IOExceptionInterface
{
private $path;
public function __construct(string $message, int $code = 0, \Throwable $previous = null, string $path = null)
{
$this->path = $path;
parent::__construct($message, $code, $previous);
}
/**
* {@inheritdoc}
*/
public function getPath()
{
return $this->path;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Filesystem\Exception;
/**
* @author Christian Flothmann <christian.flothmann@sensiolabs.de>
*/
class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Filesystem\Exception;
/**
* Exception class thrown when a file couldn't be found.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Christian Gärtner <christiangaertner.film@googlemail.com>
*/
class FileNotFoundException extends IOException
{
public function __construct(string $message = null, int $code = 0, \Throwable $previous = null, string $path = null)
{
if (null === $message) {
if (null === $path) {
$message = 'File could not be found.';
} else {
$message = sprintf('File "%s" could not be found.', $path);
}
}
parent::__construct($message, $code, $previous, $path);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Filesystem;
use Symfony\Component\Filesystem\Exception\FileNotFoundException;
use Symfony\Component\Filesystem\Exception\InvalidArgumentException;
use Symfony\Component\Filesystem\Exception\IOException;
/**
* Provides basic utility to manipulate the file system.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class Filesystem
{
private static $lastError;
/**
* Copies a file.
*
* If the target file is older than the origin file, it's always overwritten.
* If the target file is newer, it is overwritten only when the
* $overwriteNewerFiles option is set to true.
*
* @throws FileNotFoundException When originFile doesn't exist
* @throws IOException When copy fails
*/
public function copy(string $originFile, string $targetFile, bool $overwriteNewerFiles = false)
{
$originIsLocal = stream_is_local($originFile) || 0 === stripos($originFile, 'file://');
if ($originIsLocal && !is_file($originFile)) {
throw new FileNotFoundException(sprintf('Failed to copy "%s" because file does not exist.', $originFile), 0, null, $originFile);
}
$this->mkdir(\dirname($targetFile));
$doCopy = true;
if (!$overwriteNewerFiles && null === parse_url($originFile, \PHP_URL_HOST) && is_file($targetFile)) {
$doCopy = filemtime($originFile) > filemtime($targetFile);
}
if ($doCopy) {
// https://bugs.php.net/64634
if (false === $source = @fopen($originFile, 'r')) {
throw new IOException(sprintf('Failed to copy "%s" to "%s" because source file could not be opened for reading.', $originFile, $targetFile), 0, null, $originFile);
}
// Stream context created to allow files overwrite when using FTP stream wrapper - disabled by default
if (false === $target = @fopen($targetFile, 'w', null, stream_context_create(['ftp' => ['overwrite' => true]]))) {
throw new IOException(sprintf('Failed to copy "%s" to "%s" because target file could not be opened for writing.', $originFile, $targetFile), 0, null, $originFile);
}
$bytesCopied = stream_copy_to_stream($source, $target);
fclose($source);
fclose($target);
unset($source, $target);
if (!is_file($targetFile)) {
throw new IOException(sprintf('Failed to copy "%s" to "%s".', $originFile, $targetFile), 0, null, $originFile);
}
if ($originIsLocal) {
// Like `cp`, preserve executable permission bits
@chmod($targetFile, fileperms($targetFile) | (fileperms($originFile) & 0111));
if ($bytesCopied !== $bytesOrigin = filesize($originFile)) {
throw new IOException(sprintf('Failed to copy the whole content of "%s" to "%s" (%g of %g bytes copied).', $originFile, $targetFile, $bytesCopied, $bytesOrigin), 0, null, $originFile);
}
}
}
}
/**
* Creates a directory recursively.
*
* @param string|iterable $dirs The directory path
*
* @throws IOException On any directory creation failure
*/
public function mkdir($dirs, int $mode = 0777)
{
foreach ($this->toIterable($dirs) as $dir) {
if (is_dir($dir)) {
continue;
}
if (!self::box('mkdir', $dir, $mode, true)) {
if (!is_dir($dir)) {
// The directory was not created by a concurrent process. Let's throw an exception with a developer friendly error message if we have one
if (self::$lastError) {
throw new IOException(sprintf('Failed to create "%s": ', $dir).self::$lastError, 0, null, $dir);
}
throw new IOException(sprintf('Failed to create "%s".', $dir), 0, null, $dir);
}
}
}
}
/**
* Checks the existence of files or directories.
*
* @param string|iterable $files A filename, an array of files, or a \Traversable instance to check
*
* @return bool true if the file exists, false otherwise
*/
public function exists($files)
{
$maxPathLength = \PHP_MAXPATHLEN - 2;
foreach ($this->toIterable($files) as $file) {
if (\strlen($file) > $maxPathLength) {
throw new IOException(sprintf('Could not check if file exist because path length exceeds %d characters.', $maxPathLength), 0, null, $file);
}
if (!file_exists($file)) {
return false;
}
}
return true;
}
/**
* Sets access and modification time of file.
*
* @param string|iterable $files A filename, an array of files, or a \Traversable instance to create
* @param int|null $time The touch time as a Unix timestamp, if not supplied the current system time is used
* @param int|null $atime The access time as a Unix timestamp, if not supplied the current system time is used
*
* @throws IOException When touch fails
*/
public function touch($files, int $time = null, int $atime = null)
{
foreach ($this->toIterable($files) as $file) {
$touch = $time ? @touch($file, $time, $atime) : @touch($file);
if (true !== $touch) {
throw new IOException(sprintf('Failed to touch "%s".', $file), 0, null, $file);
}
}
}
/**
* Removes files or directories.
*
* @param string|iterable $files A filename, an array of files, or a \Traversable instance to remove
*
* @throws IOException When removal fails
*/
public function remove($files)
{
if ($files instanceof \Traversable) {
$files = iterator_to_array($files, false);
} elseif (!\is_array($files)) {
$files = [$files];
}
$files = array_reverse($files);
foreach ($files as $file) {
if (is_link($file)) {
// See https://bugs.php.net/52176
if (!(self::box('unlink', $file) || '\\' !== \DIRECTORY_SEPARATOR || self::box('rmdir', $file)) && file_exists($file)) {
throw new IOException(sprintf('Failed to remove symlink "%s": ', $file).self::$lastError);
}
} elseif (is_dir($file)) {
$this->remove(new \FilesystemIterator($file, \FilesystemIterator::CURRENT_AS_PATHNAME | \FilesystemIterator::SKIP_DOTS));
if (!self::box('rmdir', $file) && file_exists($file)) {
throw new IOException(sprintf('Failed to remove directory "%s": ', $file).self::$lastError);
}
} elseif (!self::box('unlink', $file) && (false !== strpos(self::$lastError, 'Permission denied') || file_exists($file))) {
throw new IOException(sprintf('Failed to remove file "%s": ', $file).self::$lastError);
}
}
}
/**
* Change mode for an array of files or directories.
*
* @param string|iterable $files A filename, an array of files, or a \Traversable instance to change mode
* @param int $mode The new mode (octal)
* @param int $umask The mode mask (octal)
* @param bool $recursive Whether change the mod recursively or not
*
* @throws IOException When the change fails
*/
public function chmod($files, int $mode, int $umask = 0000, bool $recursive = false)
{
foreach ($this->toIterable($files) as $file) {
if ((\PHP_VERSION_ID < 80000 || \is_int($mode)) && true !== @chmod($file, $mode & ~$umask)) {
throw new IOException(sprintf('Failed to chmod file "%s".', $file), 0, null, $file);
}
if ($recursive && is_dir($file) && !is_link($file)) {
$this->chmod(new \FilesystemIterator($file), $mode, $umask, true);
}
}
}
/**
* Change the owner of an array of files or directories.
*
* @param string|iterable $files A filename, an array of files, or a \Traversable instance to change owner
* @param string|int $user A user name or number
* @param bool $recursive Whether change the owner recursively or not
*
* @throws IOException When the change fails
*/
public function chown($files, $user, bool $recursive = false)
{
foreach ($this->toIterable($files) as $file) {
if ($recursive && is_dir($file) && !is_link($file)) {
$this->chown(new \FilesystemIterator($file), $user, true);
}
if (is_link($file) && \function_exists('lchown')) {
if (true !== @lchown($file, $user)) {
throw new IOException(sprintf('Failed to chown file "%s".', $file), 0, null, $file);
}
} else {
if (true !== @chown($file, $user)) {
throw new IOException(sprintf('Failed to chown file "%s".', $file), 0, null, $file);
}
}
}
}
/**
* Change the group of an array of files or directories.
*
* @param string|iterable $files A filename, an array of files, or a \Traversable instance to change group
* @param string|int $group A group name or number
* @param bool $recursive Whether change the group recursively or not
*
* @throws IOException When the change fails
*/
public function chgrp($files, $group, bool $recursive = false)
{
foreach ($this->toIterable($files) as $file) {
if ($recursive && is_dir($file) && !is_link($file)) {
$this->chgrp(new \FilesystemIterator($file), $group, true);
}
if (is_link($file) && \function_exists('lchgrp')) {
if (true !== @lchgrp($file, $group)) {
throw new IOException(sprintf('Failed to chgrp file "%s".', $file), 0, null, $file);
}
} else {
if (true !== @chgrp($file, $group)) {
throw new IOException(sprintf('Failed to chgrp file "%s".', $file), 0, null, $file);
}
}
}
}
/**
* Renames a file or a directory.
*
* @throws IOException When target file or directory already exists
* @throws IOException When origin cannot be renamed
*/
public function rename(string $origin, string $target, bool $overwrite = false)
{
// we check that target does not exist
if (!$overwrite && $this->isReadable($target)) {
throw new IOException(sprintf('Cannot rename because the target "%s" already exists.', $target), 0, null, $target);
}
if (true !== @rename($origin, $target)) {
if (is_dir($origin)) {
// See https://bugs.php.net/54097 & https://php.net/rename#113943
$this->mirror($origin, $target, null, ['override' => $overwrite, 'delete' => $overwrite]);
$this->remove($origin);
return;
}
throw new IOException(sprintf('Cannot rename "%s" to "%s".', $origin, $target), 0, null, $target);
}
}
/**
* Tells whether a file exists and is readable.
*
* @throws IOException When windows path is longer than 258 characters
*/
private function isReadable(string $filename): bool
{
$maxPathLength = \PHP_MAXPATHLEN - 2;
if (\strlen($filename) > $maxPathLength) {
throw new IOException(sprintf('Could not check if file is readable because path length exceeds %d characters.', $maxPathLength), 0, null, $filename);
}
return is_readable($filename);
}
/**
* Creates a symbolic link or copy a directory.
*
* @throws IOException When symlink fails
*/
public function symlink(string $originDir, string $targetDir, bool $copyOnWindows = false)
{
if ('\\' === \DIRECTORY_SEPARATOR) {
$originDir = strtr($originDir, '/', '\\');
$targetDir = strtr($targetDir, '/', '\\');
if ($copyOnWindows) {
$this->mirror($originDir, $targetDir);
return;
}
}
$this->mkdir(\dirname($targetDir));
if (is_link($targetDir)) {
if (readlink($targetDir) === $originDir) {
return;
}
$this->remove($targetDir);
}
if (!self::box('symlink', $originDir, $targetDir)) {
$this->linkException($originDir, $targetDir, 'symbolic');
}
}
/**
* Creates a hard link, or several hard links to a file.
*
* @param string|string[] $targetFiles The target file(s)
*
* @throws FileNotFoundException When original file is missing or not a file
* @throws IOException When link fails, including if link already exists
*/
public function hardlink(string $originFile, $targetFiles)
{
if (!$this->exists($originFile)) {
throw new FileNotFoundException(null, 0, null, $originFile);
}
if (!is_file($originFile)) {
throw new FileNotFoundException(sprintf('Origin file "%s" is not a file.', $originFile));
}
foreach ($this->toIterable($targetFiles) as $targetFile) {
if (is_file($targetFile)) {
if (fileinode($originFile) === fileinode($targetFile)) {
continue;
}
$this->remove($targetFile);
}
if (!self::box('link', $originFile, $targetFile)) {
$this->linkException($originFile, $targetFile, 'hard');
}
}
}
/**
* @param string $linkType Name of the link type, typically 'symbolic' or 'hard'
*/
private function linkException(string $origin, string $target, string $linkType)
{
if (self::$lastError) {
if ('\\' === \DIRECTORY_SEPARATOR && false !== strpos(self::$lastError, 'error code(1314)')) {
throw new IOException(sprintf('Unable to create "%s" link due to error code 1314: \'A required privilege is not held by the client\'. Do you have the required Administrator-rights?', $linkType), 0, null, $target);
}
}
throw new IOException(sprintf('Failed to create "%s" link from "%s" to "%s".', $linkType, $origin, $target), 0, null, $target);
}
/**
* Resolves links in paths.
*
* With $canonicalize = false (default)
* - if $path does not exist or is not a link, returns null
* - if $path is a link, returns the next direct target of the link without considering the existence of the target
*
* With $canonicalize = true
* - if $path does not exist, returns null
* - if $path exists, returns its absolute fully resolved final version
*
* @return string|null
*/
public function readlink(string $path, bool $canonicalize = false)
{
if (!$canonicalize && !is_link($path)) {
return null;
}
if ($canonicalize) {
if (!$this->exists($path)) {
return null;
}
if ('\\' === \DIRECTORY_SEPARATOR) {
$path = readlink($path);
}
return realpath($path);
}
if ('\\' === \DIRECTORY_SEPARATOR) {
return realpath($path);
}
return readlink($path);
}
/**
* Given an existing path, convert it to a path relative to a given starting path.
*
* @return string Path of target relative to starting path
*/
public function makePathRelative(string $endPath, string $startPath)
{
if (!$this->isAbsolutePath($startPath)) {
throw new InvalidArgumentException(sprintf('The start path "%s" is not absolute.', $startPath));
}
if (!$this->isAbsolutePath($endPath)) {
throw new InvalidArgumentException(sprintf('The end path "%s" is not absolute.', $endPath));
}
// Normalize separators on Windows
if ('\\' === \DIRECTORY_SEPARATOR) {
$endPath = str_replace('\\', '/', $endPath);
$startPath = str_replace('\\', '/', $startPath);
}
$splitDriveLetter = function ($path) {
return (\strlen($path) > 2 && ':' === $path[1] && '/' === $path[2] && ctype_alpha($path[0]))
? [substr($path, 2), strtoupper($path[0])]
: [$path, null];
};
$splitPath = function ($path) {
$result = [];
foreach (explode('/', trim($path, '/')) as $segment) {
if ('..' === $segment) {
array_pop($result);
} elseif ('.' !== $segment && '' !== $segment) {
$result[] = $segment;
}
}
return $result;
};
[$endPath, $endDriveLetter] = $splitDriveLetter($endPath);
[$startPath, $startDriveLetter] = $splitDriveLetter($startPath);
$startPathArr = $splitPath($startPath);
$endPathArr = $splitPath($endPath);
if ($endDriveLetter && $startDriveLetter && $endDriveLetter != $startDriveLetter) {
// End path is on another drive, so no relative path exists
return $endDriveLetter.':/'.($endPathArr ? implode('/', $endPathArr).'/' : '');
}
// Find for which directory the common path stops
$index = 0;
while (isset($startPathArr[$index]) && isset($endPathArr[$index]) && $startPathArr[$index] === $endPathArr[$index]) {
++$index;
}
// Determine how deep the start path is relative to the common path (ie, "web/bundles" = 2 levels)
if (1 === \count($startPathArr) && '' === $startPathArr[0]) {
$depth = 0;
} else {
$depth = \count($startPathArr) - $index;
}
// Repeated "../" for each level need to reach the common path
$traverser = str_repeat('../', $depth);
$endPathRemainder = implode('/', \array_slice($endPathArr, $index));
// Construct $endPath from traversing to the common path, then to the remaining $endPath
$relativePath = $traverser.('' !== $endPathRemainder ? $endPathRemainder.'/' : '');
return '' === $relativePath ? './' : $relativePath;
}
/**
* Mirrors a directory to another.
*
* Copies files and directories from the origin directory into the target directory. By default:
*
* - existing files in the target directory will be overwritten, except if they are newer (see the `override` option)
* - files in the target directory that do not exist in the source directory will not be deleted (see the `delete` option)
*
* @param \Traversable|null $iterator Iterator that filters which files and directories to copy, if null a recursive iterator is created
* @param array $options An array of boolean options
* Valid options are:
* - $options['override'] If true, target files newer than origin files are overwritten (see copy(), defaults to false)
* - $options['copy_on_windows'] Whether to copy files instead of links on Windows (see symlink(), defaults to false)
* - $options['delete'] Whether to delete files that are not in the source directory (defaults to false)
*
* @throws IOException When file type is unknown
*/
public function mirror(string $originDir, string $targetDir, \Traversable $iterator = null, array $options = [])
{
$targetDir = rtrim($targetDir, '/\\');
$originDir = rtrim($originDir, '/\\');
$originDirLen = \strlen($originDir);
if (!$this->exists($originDir)) {
throw new IOException(sprintf('The origin directory specified "%s" was not found.', $originDir), 0, null, $originDir);
}
// Iterate in destination folder to remove obsolete entries
if ($this->exists($targetDir) && isset($options['delete']) && $options['delete']) {
$deleteIterator = $iterator;
if (null === $deleteIterator) {
$flags = \FilesystemIterator::SKIP_DOTS;
$deleteIterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($targetDir, $flags), \RecursiveIteratorIterator::CHILD_FIRST);
}
$targetDirLen = \strlen($targetDir);
foreach ($deleteIterator as $file) {
$origin = $originDir.substr($file->getPathname(), $targetDirLen);
if (!$this->exists($origin)) {
$this->remove($file);
}
}
}
$copyOnWindows = $options['copy_on_windows'] ?? false;
if (null === $iterator) {
$flags = $copyOnWindows ? \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS : \FilesystemIterator::SKIP_DOTS;
$iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($originDir, $flags), \RecursiveIteratorIterator::SELF_FIRST);
}
$this->mkdir($targetDir);
$filesCreatedWhileMirroring = [];
foreach ($iterator as $file) {
if ($file->getPathname() === $targetDir || $file->getRealPath() === $targetDir || isset($filesCreatedWhileMirroring[$file->getRealPath()])) {
continue;
}
$target = $targetDir.substr($file->getPathname(), $originDirLen);
$filesCreatedWhileMirroring[$target] = true;
if (!$copyOnWindows && is_link($file)) {
$this->symlink($file->getLinkTarget(), $target);
} elseif (is_dir($file)) {
$this->mkdir($target);
} elseif (is_file($file)) {
$this->copy($file, $target, isset($options['override']) ? $options['override'] : false);
} else {
throw new IOException(sprintf('Unable to guess "%s" file type.', $file), 0, null, $file);
}
}
}
/**
* Returns whether the file path is an absolute path.
*
* @return bool
*/
public function isAbsolutePath(string $file)
{
return '' !== $file && (strspn($file, '/\\', 0, 1)
|| (\strlen($file) > 3 && ctype_alpha($file[0])
&& ':' === $file[1]
&& strspn($file, '/\\', 2, 1)
)
|| null !== parse_url($file, \PHP_URL_SCHEME)
);
}
/**
* Creates a temporary file with support for custom stream wrappers.
*
* @param string $prefix The prefix of the generated temporary filename
* Note: Windows uses only the first three characters of prefix
* @param string $suffix The suffix of the generated temporary filename
*
* @return string The new temporary filename (with path), or throw an exception on failure
*/
public function tempnam(string $dir, string $prefix/*, string $suffix = ''*/)
{
$suffix = \func_num_args() > 2 ? func_get_arg(2) : '';
[$scheme, $hierarchy] = $this->getSchemeAndHierarchy($dir);
// If no scheme or scheme is "file" or "gs" (Google Cloud) create temp file in local filesystem
if ((null === $scheme || 'file' === $scheme || 'gs' === $scheme) && '' === $suffix) {
$tmpFile = @tempnam($hierarchy, $prefix);
// If tempnam failed or no scheme return the filename otherwise prepend the scheme
if (false !== $tmpFile) {
if (null !== $scheme && 'gs' !== $scheme) {
return $scheme.'://'.$tmpFile;
}
return $tmpFile;
}
throw new IOException('A temporary file could not be created.');
}
// Loop until we create a valid temp file or have reached 10 attempts
for ($i = 0; $i < 10; ++$i) {
// Create a unique filename
$tmpFile = $dir.'/'.$prefix.uniqid(mt_rand(), true).$suffix;
// Use fopen instead of file_exists as some streams do not support stat
// Use mode 'x+' to atomically check existence and create to avoid a TOCTOU vulnerability
$handle = @fopen($tmpFile, 'x+');
// If unsuccessful restart the loop
if (false === $handle) {
continue;
}
// Close the file if it was successfully opened
@fclose($handle);
return $tmpFile;
}
throw new IOException('A temporary file could not be created.');
}
/**
* Atomically dumps content into a file.
*
* @param string|resource $content The data to write into the file
*
* @throws IOException if the file cannot be written to
*/
public function dumpFile(string $filename, $content)
{
if (\is_array($content)) {
throw new \TypeError(sprintf('Argument 2 passed to "%s()" must be string or resource, array given.', __METHOD__));
}
$dir = \dirname($filename);
if (!is_dir($dir)) {
$this->mkdir($dir);
}
if (!is_writable($dir)) {
throw new IOException(sprintf('Unable to write to the "%s" directory.', $dir), 0, null, $dir);
}
// Will create a temp file with 0600 access rights
// when the filesystem supports chmod.
$tmpFile = $this->tempnam($dir, basename($filename));
try {
if (false === @file_put_contents($tmpFile, $content)) {
throw new IOException(sprintf('Failed to write file "%s".', $filename), 0, null, $filename);
}
@chmod($tmpFile, file_exists($filename) ? fileperms($filename) : 0666 & ~umask());
$this->rename($tmpFile, $filename, true);
} finally {
@unlink($tmpFile);
}
}
/**
* Appends content to an existing file.
*
* @param string|resource $content The content to append
*
* @throws IOException If the file is not writable
*/
public function appendToFile(string $filename, $content)
{
if (\is_array($content)) {
throw new \TypeError(sprintf('Argument 2 passed to "%s()" must be string or resource, array given.', __METHOD__));
}
$dir = \dirname($filename);
if (!is_dir($dir)) {
$this->mkdir($dir);
}
if (!is_writable($dir)) {
throw new IOException(sprintf('Unable to write to the "%s" directory.', $dir), 0, null, $dir);
}
if (false === @file_put_contents($filename, $content, \FILE_APPEND)) {
throw new IOException(sprintf('Failed to write file "%s".', $filename), 0, null, $filename);
}
}
private function toIterable($files): iterable
{
return \is_array($files) || $files instanceof \Traversable ? $files : [$files];
}
/**
* Gets a 2-tuple of scheme (may be null) and hierarchical part of a filename (e.g. file:///tmp -> [file, tmp]).
*/
private function getSchemeAndHierarchy(string $filename): array
{
$components = explode('://', $filename, 2);
return 2 === \count($components) ? [$components[0], $components[1]] : [null, $components[0]];
}
/**
* @return mixed
*/
private static function box(callable $func)
{
self::$lastError = null;
set_error_handler(__CLASS__.'::handleError');
try {
$result = $func(...\array_slice(\func_get_args(), 1));
restore_error_handler();
return $result;
} catch (\Throwable $e) {
}
restore_error_handler();
throw $e;
}
/**
* @internal
*/
public static function handleError($type, $msg)
{
self::$lastError = $msg;
}
}
Copyright (c) 2004-2020 Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Exception;
/**
* @author Andreas Erhard <andreas.erhard@i-med.ac.at>
*/
class DirectoryNotFoundException extends \InvalidArgumentException
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Exception;
/**
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
*/
class AccessDeniedException extends \UnexpectedValueException
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder;
/**
* Glob matches globbing patterns against text.
*
* if match_glob("foo.*", "foo.bar") echo "matched\n";
*
* // prints foo.bar and foo.baz
* $regex = glob_to_regex("foo.*");
* for (['foo.bar', 'foo.baz', 'foo', 'bar'] as $t)
* {
* if (/$regex/) echo "matched: $car\n";
* }
*
* Glob implements glob(3) style matching that can be used to match
* against text, rather than fetching names from a filesystem.
*
* Based on the Perl Text::Glob module.
*
* @author Fabien Potencier <fabien@symfony.com> PHP port
* @author Richard Clamp <richardc@unixbeard.net> Perl version
* @copyright 2004-2005 Fabien Potencier <fabien@symfony.com>
* @copyright 2002 Richard Clamp <richardc@unixbeard.net>
*/
class Glob
{
/**
* Returns a regexp which is the equivalent of the glob pattern.
*
* @return string
*/
public static function toRegex(string $glob, bool $strictLeadingDot = true, bool $strictWildcardSlash = true, string $delimiter = '#')
{
$firstByte = true;
$escaping = false;
$inCurlies = 0;
$regex = '';
$sizeGlob = \strlen($glob);
for ($i = 0; $i < $sizeGlob; ++$i) {
$car = $glob[$i];
if ($firstByte && $strictLeadingDot && '.' !== $car) {
$regex .= '(?=[^\.])';
}
$firstByte = '/' === $car;
if ($firstByte && $strictWildcardSlash && isset($glob[$i + 2]) && '**' === $glob[$i + 1].$glob[$i + 2] && (!isset($glob[$i + 3]) || '/' === $glob[$i + 3])) {
$car = '[^/]++/';
if (!isset($glob[$i + 3])) {
$car .= '?';
}
if ($strictLeadingDot) {
$car = '(?=[^\.])'.$car;
}
$car = '/(?:'.$car.')*';
$i += 2 + isset($glob[$i + 3]);
if ('/' === $delimiter) {
$car = str_replace('/', '\\/', $car);
}
}
if ($delimiter === $car || '.' === $car || '(' === $car || ')' === $car || '|' === $car || '+' === $car || '^' === $car || '$' === $car) {
$regex .= "\\$car";
} elseif ('*' === $car) {
$regex .= $escaping ? '\\*' : ($strictWildcardSlash ? '[^/]*' : '.*');
} elseif ('?' === $car) {
$regex .= $escaping ? '\\?' : ($strictWildcardSlash ? '[^/]' : '.');
} elseif ('{' === $car) {
$regex .= $escaping ? '\\{' : '(';
if (!$escaping) {
++$inCurlies;
}
} elseif ('}' === $car && $inCurlies) {
$regex .= $escaping ? '}' : ')';
if (!$escaping) {
--$inCurlies;
}
} elseif (',' === $car && $inCurlies) {
$regex .= $escaping ? ',' : '|';
} elseif ('\\' === $car) {
if ($escaping) {
$regex .= '\\\\';
$escaping = false;
} else {
$escaping = true;
}
continue;
} else {
$regex .= $car;
}
$escaping = false;
}
return $delimiter.'^'.$regex.'$'.$delimiter;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder;
use Symfony\Component\Finder\Comparator\DateComparator;
use Symfony\Component\Finder\Comparator\NumberComparator;
use Symfony\Component\Finder\Exception\DirectoryNotFoundException;
use Symfony\Component\Finder\Iterator\CustomFilterIterator;
use Symfony\Component\Finder\Iterator\DateRangeFilterIterator;
use Symfony\Component\Finder\Iterator\DepthRangeFilterIterator;
use Symfony\Component\Finder\Iterator\ExcludeDirectoryFilterIterator;
use Symfony\Component\Finder\Iterator\FilecontentFilterIterator;
use Symfony\Component\Finder\Iterator\FilenameFilterIterator;
use Symfony\Component\Finder\Iterator\SizeRangeFilterIterator;
use Symfony\Component\Finder\Iterator\SortableIterator;
/**
* Finder allows to build rules to find files and directories.
*
* It is a thin wrapper around several specialized iterator classes.
*
* All rules may be invoked several times.
*
* All methods return the current Finder object to allow chaining:
*
* $finder = Finder::create()->files()->name('*.php')->in(__DIR__);
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class Finder implements \IteratorAggregate, \Countable
{
const IGNORE_VCS_FILES = 1;
const IGNORE_DOT_FILES = 2;
const IGNORE_VCS_IGNORED_FILES = 4;
private $mode = 0;
private $names = [];
private $notNames = [];
private $exclude = [];
private $filters = [];
private $depths = [];
private $sizes = [];
private $followLinks = false;
private $reverseSorting = false;
private $sort = false;
private $ignore = 0;
private $dirs = [];
private $dates = [];
private $iterators = [];
private $contains = [];
private $notContains = [];
private $paths = [];
private $notPaths = [];
private $ignoreUnreadableDirs = false;
private static $vcsPatterns = ['.svn', '_svn', 'CVS', '_darcs', '.arch-params', '.monotone', '.bzr', '.git', '.hg'];
public function __construct()
{
$this->ignore = static::IGNORE_VCS_FILES | static::IGNORE_DOT_FILES;
}
/**
* Creates a new Finder.
*
* @return static
*/
public static function create()
{
return new static();
}
/**
* Restricts the matching to directories only.
*
* @return $this
*/
public function directories()
{
$this->mode = Iterator\FileTypeFilterIterator::ONLY_DIRECTORIES;
return $this;
}
/**
* Restricts the matching to files only.
*
* @return $this
*/
public function files()
{
$this->mode = Iterator\FileTypeFilterIterator::ONLY_FILES;
return $this;
}
/**
* Adds tests for the directory depth.
*
* Usage:
*
* $finder->depth('> 1') // the Finder will start matching at level 1.
* $finder->depth('< 3') // the Finder will descend at most 3 levels of directories below the starting point.
* $finder->depth(['>= 1', '< 3'])
*
* @param string|int|string[]|int[] $levels The depth level expression or an array of depth levels
*
* @return $this
*
* @see DepthRangeFilterIterator
* @see NumberComparator
*/
public function depth($levels)
{
foreach ((array) $levels as $level) {
$this->depths[] = new Comparator\NumberComparator($level);
}
return $this;
}
/**
* Adds tests for file dates (last modified).
*
* The date must be something that strtotime() is able to parse:
*
* $finder->date('since yesterday');
* $finder->date('until 2 days ago');
* $finder->date('> now - 2 hours');
* $finder->date('>= 2005-10-15');
* $finder->date(['>= 2005-10-15', '<= 2006-05-27']);
*
* @param string|string[] $dates A date range string or an array of date ranges
*
* @return $this
*
* @see strtotime
* @see DateRangeFilterIterator
* @see DateComparator
*/
public function date($dates)
{
foreach ((array) $dates as $date) {
$this->dates[] = new Comparator\DateComparator($date);
}
return $this;
}
/**
* Adds rules that files must match.
*
* You can use patterns (delimited with / sign), globs or simple strings.
*
* $finder->name('*.php')
* $finder->name('/\.php$/') // same as above
* $finder->name('test.php')
* $finder->name(['test.py', 'test.php'])
*
* @param string|string[] $patterns A pattern (a regexp, a glob, or a string) or an array of patterns
*
* @return $this
*
* @see FilenameFilterIterator
*/
public function name($patterns)
{
$this->names = array_merge($this->names, (array) $patterns);
return $this;
}
/**
* Adds rules that files must not match.
*
* @param string|string[] $patterns A pattern (a regexp, a glob, or a string) or an array of patterns
*
* @return $this
*
* @see FilenameFilterIterator
*/
public function notName($patterns)
{
$this->notNames = array_merge($this->notNames, (array) $patterns);
return $this;
}
/**
* Adds tests that file contents must match.
*
* Strings or PCRE patterns can be used:
*
* $finder->contains('Lorem ipsum')
* $finder->contains('/Lorem ipsum/i')
* $finder->contains(['dolor', '/ipsum/i'])
*
* @param string|string[] $patterns A pattern (string or regexp) or an array of patterns
*
* @return $this
*
* @see FilecontentFilterIterator
*/
public function contains($patterns)
{
$this->contains = array_merge($this->contains, (array) $patterns);
return $this;
}
/**
* Adds tests that file contents must not match.
*
* Strings or PCRE patterns can be used:
*
* $finder->notContains('Lorem ipsum')
* $finder->notContains('/Lorem ipsum/i')
* $finder->notContains(['lorem', '/dolor/i'])
*
* @param string|string[] $patterns A pattern (string or regexp) or an array of patterns
*
* @return $this
*
* @see FilecontentFilterIterator
*/
public function notContains($patterns)
{
$this->notContains = array_merge($this->notContains, (array) $patterns);
return $this;
}
/**
* Adds rules that filenames must match.
*
* You can use patterns (delimited with / sign) or simple strings.
*
* $finder->path('some/special/dir')
* $finder->path('/some\/special\/dir/') // same as above
* $finder->path(['some dir', 'another/dir'])
*
* Use only / as dirname separator.
*
* @param string|string[] $patterns A pattern (a regexp or a string) or an array of patterns
*
* @return $this
*
* @see FilenameFilterIterator
*/
public function path($patterns)
{
$this->paths = array_merge($this->paths, (array) $patterns);
return $this;
}
/**
* Adds rules that filenames must not match.
*
* You can use patterns (delimited with / sign) or simple strings.
*
* $finder->notPath('some/special/dir')
* $finder->notPath('/some\/special\/dir/') // same as above
* $finder->notPath(['some/file.txt', 'another/file.log'])
*
* Use only / as dirname separator.
*
* @param string|string[] $patterns A pattern (a regexp or a string) or an array of patterns
*
* @return $this
*
* @see FilenameFilterIterator
*/
public function notPath($patterns)
{
$this->notPaths = array_merge($this->notPaths, (array) $patterns);
return $this;
}
/**
* Adds tests for file sizes.
*
* $finder->size('> 10K');
* $finder->size('<= 1Ki');
* $finder->size(4);
* $finder->size(['> 10K', '< 20K'])
*
* @param string|int|string[]|int[] $sizes A size range string or an integer or an array of size ranges
*
* @return $this
*
* @see SizeRangeFilterIterator
* @see NumberComparator
*/
public function size($sizes)
{
foreach ((array) $sizes as $size) {
$this->sizes[] = new Comparator\NumberComparator($size);
}
return $this;
}
/**
* Excludes directories.
*
* Directories passed as argument must be relative to the ones defined with the `in()` method. For example:
*
* $finder->in(__DIR__)->exclude('ruby');
*
* @param string|array $dirs A directory path or an array of directories
*
* @return $this
*
* @see ExcludeDirectoryFilterIterator
*/
public function exclude($dirs)
{
$this->exclude = array_merge($this->exclude, (array) $dirs);
return $this;
}
/**
* Excludes "hidden" directories and files (starting with a dot).
*
* This option is enabled by default.
*
* @return $this
*
* @see ExcludeDirectoryFilterIterator
*/
public function ignoreDotFiles(bool $ignoreDotFiles)
{
if ($ignoreDotFiles) {
$this->ignore |= static::IGNORE_DOT_FILES;
} else {
$this->ignore &= ~static::IGNORE_DOT_FILES;
}
return $this;
}
/**
* Forces the finder to ignore version control directories.
*
* This option is enabled by default.
*
* @return $this
*
* @see ExcludeDirectoryFilterIterator
*/
public function ignoreVCS(bool $ignoreVCS)
{
if ($ignoreVCS) {
$this->ignore |= static::IGNORE_VCS_FILES;
} else {
$this->ignore &= ~static::IGNORE_VCS_FILES;
}
return $this;
}
/**
* Forces Finder to obey .gitignore and ignore files based on rules listed there.
*
* This option is disabled by default.
*
* @return $this
*/
public function ignoreVCSIgnored(bool $ignoreVCSIgnored)
{
if ($ignoreVCSIgnored) {
$this->ignore |= static::IGNORE_VCS_IGNORED_FILES;
} else {
$this->ignore &= ~static::IGNORE_VCS_IGNORED_FILES;
}
return $this;
}
/**
* Adds VCS patterns.
*
* @see ignoreVCS()
*
* @param string|string[] $pattern VCS patterns to ignore
*/
public static function addVCSPattern($pattern)
{
foreach ((array) $pattern as $p) {
self::$vcsPatterns[] = $p;
}
self::$vcsPatterns = array_unique(self::$vcsPatterns);
}
/**
* Sorts files and directories by an anonymous function.
*
* The anonymous function receives two \SplFileInfo instances to compare.
*
* This can be slow as all the matching files and directories must be retrieved for comparison.
*
* @return $this
*
* @see SortableIterator
*/
public function sort(\Closure $closure)
{
$this->sort = $closure;
return $this;
}
/**
* Sorts files and directories by name.
*
* This can be slow as all the matching files and directories must be retrieved for comparison.
*
* @return $this
*
* @see SortableIterator
*/
public function sortByName(bool $useNaturalSort = false)
{
$this->sort = $useNaturalSort ? Iterator\SortableIterator::SORT_BY_NAME_NATURAL : Iterator\SortableIterator::SORT_BY_NAME;
return $this;
}
/**
* Sorts files and directories by type (directories before files), then by name.
*
* This can be slow as all the matching files and directories must be retrieved for comparison.
*
* @return $this
*
* @see SortableIterator
*/
public function sortByType()
{
$this->sort = Iterator\SortableIterator::SORT_BY_TYPE;
return $this;
}
/**
* Sorts files and directories by the last accessed time.
*
* This is the time that the file was last accessed, read or written to.
*
* This can be slow as all the matching files and directories must be retrieved for comparison.
*
* @return $this
*
* @see SortableIterator
*/
public function sortByAccessedTime()
{
$this->sort = Iterator\SortableIterator::SORT_BY_ACCESSED_TIME;
return $this;
}
/**
* Reverses the sorting.
*
* @return $this
*/
public function reverseSorting()
{
$this->reverseSorting = true;
return $this;
}
/**
* Sorts files and directories by the last inode changed time.
*
* This is the time that the inode information was last modified (permissions, owner, group or other metadata).
*
* On Windows, since inode is not available, changed time is actually the file creation time.
*
* This can be slow as all the matching files and directories must be retrieved for comparison.
*
* @return $this
*
* @see SortableIterator
*/
public function sortByChangedTime()
{
$this->sort = Iterator\SortableIterator::SORT_BY_CHANGED_TIME;
return $this;
}
/**
* Sorts files and directories by the last modified time.
*
* This is the last time the actual contents of the file were last modified.
*
* This can be slow as all the matching files and directories must be retrieved for comparison.
*
* @return $this
*
* @see SortableIterator
*/
public function sortByModifiedTime()
{
$this->sort = Iterator\SortableIterator::SORT_BY_MODIFIED_TIME;
return $this;
}
/**
* Filters the iterator with an anonymous function.
*
* The anonymous function receives a \SplFileInfo and must return false
* to remove files.
*
* @return $this
*
* @see CustomFilterIterator
*/
public function filter(\Closure $closure)
{
$this->filters[] = $closure;
return $this;
}
/**
* Forces the following of symlinks.
*
* @return $this
*/
public function followLinks()
{
$this->followLinks = true;
return $this;
}
/**
* Tells finder to ignore unreadable directories.
*
* By default, scanning unreadable directories content throws an AccessDeniedException.
*
* @return $this
*/
public function ignoreUnreadableDirs(bool $ignore = true)
{
$this->ignoreUnreadableDirs = $ignore;
return $this;
}
/**
* Searches files and directories which match defined rules.
*
* @param string|string[] $dirs A directory path or an array of directories
*
* @return $this
*
* @throws DirectoryNotFoundException if one of the directories does not exist
*/
public function in($dirs)
{
$resolvedDirs = [];
foreach ((array) $dirs as $dir) {
if (is_dir($dir)) {
$resolvedDirs[] = $this->normalizeDir($dir);
} elseif ($glob = glob($dir, (\defined('GLOB_BRACE') ? \GLOB_BRACE : 0) | \GLOB_ONLYDIR | \GLOB_NOSORT)) {
sort($glob);
$resolvedDirs = array_merge($resolvedDirs, array_map([$this, 'normalizeDir'], $glob));
} else {
throw new DirectoryNotFoundException(sprintf('The "%s" directory does not exist.', $dir));
}
}
$this->dirs = array_merge($this->dirs, $resolvedDirs);
return $this;
}
/**
* Returns an Iterator for the current Finder configuration.
*
* This method implements the IteratorAggregate interface.
*
* @return \Iterator|SplFileInfo[] An iterator
*
* @throws \LogicException if the in() method has not been called
*/
public function getIterator()
{
if (0 === \count($this->dirs) && 0 === \count($this->iterators)) {
throw new \LogicException('You must call one of in() or append() methods before iterating over a Finder.');
}
if (1 === \count($this->dirs) && 0 === \count($this->iterators)) {
return $this->searchInDirectory($this->dirs[0]);
}
$iterator = new \AppendIterator();
foreach ($this->dirs as $dir) {
$iterator->append($this->searchInDirectory($dir));
}
foreach ($this->iterators as $it) {
$iterator->append($it);
}
return $iterator;
}
/**
* Appends an existing set of files/directories to the finder.
*
* The set can be another Finder, an Iterator, an IteratorAggregate, or even a plain array.
*
* @return $this
*
* @throws \InvalidArgumentException when the given argument is not iterable
*/
public function append(iterable $iterator)
{
if ($iterator instanceof \IteratorAggregate) {
$this->iterators[] = $iterator->getIterator();
} elseif ($iterator instanceof \Iterator) {
$this->iterators[] = $iterator;
} elseif ($iterator instanceof \Traversable || \is_array($iterator)) {
$it = new \ArrayIterator();
foreach ($iterator as $file) {
$it->append($file instanceof \SplFileInfo ? $file : new \SplFileInfo($file));
}
$this->iterators[] = $it;
} else {
throw new \InvalidArgumentException('Finder::append() method wrong argument type.');
}
return $this;
}
/**
* Check if any results were found.
*
* @return bool
*/
public function hasResults()
{
foreach ($this->getIterator() as $_) {
return true;
}
return false;
}
/**
* Counts all the results collected by the iterators.
*
* @return int
*/
public function count()
{
return iterator_count($this->getIterator());
}
private function searchInDirectory(string $dir): \Iterator
{
$exclude = $this->exclude;
$notPaths = $this->notPaths;
if (static::IGNORE_VCS_FILES === (static::IGNORE_VCS_FILES & $this->ignore)) {
$exclude = array_merge($exclude, self::$vcsPatterns);
}
if (static::IGNORE_DOT_FILES === (static::IGNORE_DOT_FILES & $this->ignore)) {
$notPaths[] = '#(^|/)\..+(/|$)#';
}
if (static::IGNORE_VCS_IGNORED_FILES === (static::IGNORE_VCS_IGNORED_FILES & $this->ignore)) {
$gitignoreFilePath = sprintf('%s/.gitignore', $dir);
if (!is_readable($gitignoreFilePath)) {
throw new \RuntimeException(sprintf('The "ignoreVCSIgnored" option cannot be used by the Finder as the "%s" file is not readable.', $gitignoreFilePath));
}
$notPaths = array_merge($notPaths, [Gitignore::toRegex(file_get_contents($gitignoreFilePath))]);
}
$minDepth = 0;
$maxDepth = \PHP_INT_MAX;
foreach ($this->depths as $comparator) {
switch ($comparator->getOperator()) {
case '>':
$minDepth = $comparator->getTarget() + 1;
break;
case '>=':
$minDepth = $comparator->getTarget();
break;
case '<':
$maxDepth = $comparator->getTarget() - 1;
break;
case '<=':
$maxDepth = $comparator->getTarget();
break;
default:
$minDepth = $maxDepth = $comparator->getTarget();
}
}
$flags = \RecursiveDirectoryIterator::SKIP_DOTS;
if ($this->followLinks) {
$flags |= \RecursiveDirectoryIterator::FOLLOW_SYMLINKS;
}
$iterator = new Iterator\RecursiveDirectoryIterator($dir, $flags, $this->ignoreUnreadableDirs);
if ($exclude) {
$iterator = new Iterator\ExcludeDirectoryFilterIterator($iterator, $exclude);
}
$iterator = new \RecursiveIteratorIterator($iterator, \RecursiveIteratorIterator::SELF_FIRST);
if ($minDepth > 0 || $maxDepth < \PHP_INT_MAX) {
$iterator = new Iterator\DepthRangeFilterIterator($iterator, $minDepth, $maxDepth);
}
if ($this->mode) {
$iterator = new Iterator\FileTypeFilterIterator($iterator, $this->mode);
}
if ($this->names || $this->notNames) {
$iterator = new Iterator\FilenameFilterIterator($iterator, $this->names, $this->notNames);
}
if ($this->contains || $this->notContains) {
$iterator = new Iterator\FilecontentFilterIterator($iterator, $this->contains, $this->notContains);
}
if ($this->sizes) {
$iterator = new Iterator\SizeRangeFilterIterator($iterator, $this->sizes);
}
if ($this->dates) {
$iterator = new Iterator\DateRangeFilterIterator($iterator, $this->dates);
}
if ($this->filters) {
$iterator = new Iterator\CustomFilterIterator($iterator, $this->filters);
}
if ($this->paths || $notPaths) {
$iterator = new Iterator\PathFilterIterator($iterator, $this->paths, $notPaths);
}
if ($this->sort || $this->reverseSorting) {
$iteratorAggregate = new Iterator\SortableIterator($iterator, $this->sort, $this->reverseSorting);
$iterator = $iteratorAggregate->getIterator();
}
return $iterator;
}
/**
* Normalizes given directory names by removing trailing slashes.
*
* Excluding: (s)ftp:// or ssh2.(s)ftp:// wrapper
*/
private function normalizeDir(string $dir): string
{
if ('/' === $dir) {
return $dir;
}
$dir = rtrim($dir, '/'.\DIRECTORY_SEPARATOR);
if (preg_match('#^(ssh2\.)?s?ftp://#', $dir)) {
$dir .= '/';
}
return $dir;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder;
/**
* Extends \SplFileInfo to support relative paths.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class SplFileInfo extends \SplFileInfo
{
private $relativePath;
private $relativePathname;
/**
* @param string $file The file name
* @param string $relativePath The relative path
* @param string $relativePathname The relative path name
*/
public function __construct(string $file, string $relativePath, string $relativePathname)
{
parent::__construct($file);
$this->relativePath = $relativePath;
$this->relativePathname = $relativePathname;
}
/**
* Returns the relative path.
*
* This path does not contain the file name.
*
* @return string the relative path
*/
public function getRelativePath()
{
return $this->relativePath;
}
/**
* Returns the relative path name.
*
* This path contains the file name.
*
* @return string the relative path name
*/
public function getRelativePathname()
{
return $this->relativePathname;
}
public function getFilenameWithoutExtension(): string
{
$filename = $this->getFilename();
return pathinfo($filename, \PATHINFO_FILENAME);
}
/**
* Returns the contents of the file.
*
* @return string the contents of the file
*
* @throws \RuntimeException
*/
public function getContents()
{
set_error_handler(function ($type, $msg) use (&$error) { $error = $msg; });
$content = file_get_contents($this->getPathname());
restore_error_handler();
if (false === $content) {
throw new \RuntimeException($error);
}
return $content;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Iterator;
/**
* SortableIterator applies a sort on a given Iterator.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class SortableIterator implements \IteratorAggregate
{
const SORT_BY_NONE = 0;
const SORT_BY_NAME = 1;
const SORT_BY_TYPE = 2;
const SORT_BY_ACCESSED_TIME = 3;
const SORT_BY_CHANGED_TIME = 4;
const SORT_BY_MODIFIED_TIME = 5;
const SORT_BY_NAME_NATURAL = 6;
private $iterator;
private $sort;
/**
* @param \Traversable $iterator The Iterator to filter
* @param int|callable $sort The sort type (SORT_BY_NAME, SORT_BY_TYPE, or a PHP callback)
*
* @throws \InvalidArgumentException
*/
public function __construct(\Traversable $iterator, $sort, bool $reverseOrder = false)
{
$this->iterator = $iterator;
$order = $reverseOrder ? -1 : 1;
if (self::SORT_BY_NAME === $sort) {
$this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) {
return $order * strcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname());
};
} elseif (self::SORT_BY_NAME_NATURAL === $sort) {
$this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) {
return $order * strnatcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname());
};
} elseif (self::SORT_BY_TYPE === $sort) {
$this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) {
if ($a->isDir() && $b->isFile()) {
return -$order;
} elseif ($a->isFile() && $b->isDir()) {
return $order;
}
return $order * strcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname());
};
} elseif (self::SORT_BY_ACCESSED_TIME === $sort) {
$this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) {
return $order * ($a->getATime() - $b->getATime());
};
} elseif (self::SORT_BY_CHANGED_TIME === $sort) {
$this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) {
return $order * ($a->getCTime() - $b->getCTime());
};
} elseif (self::SORT_BY_MODIFIED_TIME === $sort) {
$this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) {
return $order * ($a->getMTime() - $b->getMTime());
};
} elseif (self::SORT_BY_NONE === $sort) {
$this->sort = $order;
} elseif (\is_callable($sort)) {
$this->sort = $reverseOrder ? static function (\SplFileInfo $a, \SplFileInfo $b) use ($sort) { return -$sort($a, $b); } : $sort;
} else {
throw new \InvalidArgumentException('The SortableIterator takes a PHP callable or a valid built-in sort algorithm as an argument.');
}
}
/**
* @return \Traversable
*/
public function getIterator()
{
if (1 === $this->sort) {
return $this->iterator;
}
$array = iterator_to_array($this->iterator, true);
if (-1 === $this->sort) {
$array = array_reverse($array);
} else {
uasort($array, $this->sort);
}
return new \ArrayIterator($array);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Iterator;
/**
* DepthRangeFilterIterator limits the directory depth.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class DepthRangeFilterIterator extends \FilterIterator
{
private $minDepth = 0;
/**
* @param \RecursiveIteratorIterator $iterator The Iterator to filter
* @param int $minDepth The min depth
* @param int $maxDepth The max depth
*/
public function __construct(\RecursiveIteratorIterator $iterator, int $minDepth = 0, int $maxDepth = \PHP_INT_MAX)
{
$this->minDepth = $minDepth;
$iterator->setMaxDepth(\PHP_INT_MAX === $maxDepth ? -1 : $maxDepth);
parent::__construct($iterator);
}
/**
* Filters the iterator values.
*
* @return bool true if the value should be kept, false otherwise
*/
public function accept()
{
return $this->getInnerIterator()->getDepth() >= $this->minDepth;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Iterator;
use Symfony\Component\Finder\Comparator\NumberComparator;
/**
* SizeRangeFilterIterator filters out files that are not in the given size range.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class SizeRangeFilterIterator extends \FilterIterator
{
private $comparators = [];
/**
* @param \Iterator $iterator The Iterator to filter
* @param NumberComparator[] $comparators An array of NumberComparator instances
*/
public function __construct(\Iterator $iterator, array $comparators)
{
$this->comparators = $comparators;
parent::__construct($iterator);
}
/**
* Filters the iterator values.
*
* @return bool true if the value should be kept, false otherwise
*/
public function accept()
{
$fileinfo = $this->current();
if (!$fileinfo->isFile()) {
return true;
}
$filesize = $fileinfo->getSize();
foreach ($this->comparators as $compare) {
if (!$compare->test($filesize)) {
return false;
}
}
return true;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Iterator;
use Symfony\Component\Finder\Exception\AccessDeniedException;
use Symfony\Component\Finder\SplFileInfo;
/**
* Extends the \RecursiveDirectoryIterator to support relative paths.
*
* @author Victor Berchet <victor@suumit.com>
*/
class RecursiveDirectoryIterator extends \RecursiveDirectoryIterator
{
/**
* @var bool
*/
private $ignoreUnreadableDirs;
/**
* @var bool
*/
private $rewindable;
// these 3 properties take part of the performance optimization to avoid redoing the same work in all iterations
private $rootPath;
private $subPath;
private $directorySeparator = '/';
/**
* @throws \RuntimeException
*/
public function __construct(string $path, int $flags, bool $ignoreUnreadableDirs = false)
{
if ($flags & (self::CURRENT_AS_PATHNAME | self::CURRENT_AS_SELF)) {
throw new \RuntimeException('This iterator only support returning current as fileinfo.');
}
parent::__construct($path, $flags);
$this->ignoreUnreadableDirs = $ignoreUnreadableDirs;
$this->rootPath = $path;
if ('/' !== \DIRECTORY_SEPARATOR && !($flags & self::UNIX_PATHS)) {
$this->directorySeparator = \DIRECTORY_SEPARATOR;
}
}
/**
* Return an instance of SplFileInfo with support for relative paths.
*
* @return SplFileInfo File information
*/
public function current()
{
// the logic here avoids redoing the same work in all iterations
if (null === $subPathname = $this->subPath) {
$subPathname = $this->subPath = (string) $this->getSubPath();
}
if ('' !== $subPathname) {
$subPathname .= $this->directorySeparator;
}
$subPathname .= $this->getFilename();
if ('/' !== $basePath = $this->rootPath) {
$basePath .= $this->directorySeparator;
}
return new SplFileInfo($basePath.$subPathname, $this->subPath, $subPathname);
}
/**
* @return \RecursiveIterator
*
* @throws AccessDeniedException
*/
public function getChildren()
{
try {
$children = parent::getChildren();
if ($children instanceof self) {
// parent method will call the constructor with default arguments, so unreadable dirs won't be ignored anymore
$children->ignoreUnreadableDirs = $this->ignoreUnreadableDirs;
// performance optimization to avoid redoing the same work in all children
$children->rewindable = &$this->rewindable;
$children->rootPath = $this->rootPath;
}
return $children;
} catch (\UnexpectedValueException $e) {
if ($this->ignoreUnreadableDirs) {
// If directory is unreadable and finder is set to ignore it, a fake empty content is returned.
return new \RecursiveArrayIterator([]);
} else {
throw new AccessDeniedException($e->getMessage(), $e->getCode(), $e);
}
}
}
/**
* Do nothing for non rewindable stream.
*/
public function rewind()
{
if (false === $this->isRewindable()) {
return;
}
parent::rewind();
}
/**
* Checks if the stream is rewindable.
*
* @return bool true when the stream is rewindable, false otherwise
*/
public function isRewindable()
{
if (null !== $this->rewindable) {
return $this->rewindable;
}
if (false !== $stream = @opendir($this->getPath())) {
$infos = stream_get_meta_data($stream);
closedir($stream);
if ($infos['seekable']) {
return $this->rewindable = true;
}
}
return $this->rewindable = false;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Iterator;
/**
* FileTypeFilterIterator only keeps files, directories, or both.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class FileTypeFilterIterator extends \FilterIterator
{
const ONLY_FILES = 1;
const ONLY_DIRECTORIES = 2;
private $mode;
/**
* @param \Iterator $iterator The Iterator to filter
* @param int $mode The mode (self::ONLY_FILES or self::ONLY_DIRECTORIES)
*/
public function __construct(\Iterator $iterator, int $mode)
{
$this->mode = $mode;
parent::__construct($iterator);
}
/**
* Filters the iterator values.
*
* @return bool true if the value should be kept, false otherwise
*/
public function accept()
{
$fileinfo = $this->current();
if (self::ONLY_DIRECTORIES === (self::ONLY_DIRECTORIES & $this->mode) && $fileinfo->isFile()) {
return false;
} elseif (self::ONLY_FILES === (self::ONLY_FILES & $this->mode) && $fileinfo->isDir()) {
return false;
}
return true;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Iterator;
/**
* CustomFilterIterator filters files by applying anonymous functions.
*
* The anonymous function receives a \SplFileInfo and must return false
* to remove files.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class CustomFilterIterator extends \FilterIterator
{
private $filters = [];
/**
* @param \Iterator $iterator The Iterator to filter
* @param callable[] $filters An array of PHP callbacks
*
* @throws \InvalidArgumentException
*/
public function __construct(\Iterator $iterator, array $filters)
{
foreach ($filters as $filter) {
if (!\is_callable($filter)) {
throw new \InvalidArgumentException('Invalid PHP callback.');
}
}
$this->filters = $filters;
parent::__construct($iterator);
}
/**
* Filters the iterator values.
*
* @return bool true if the value should be kept, false otherwise
*/
public function accept()
{
$fileinfo = $this->current();
foreach ($this->filters as $filter) {
if (false === $filter($fileinfo)) {
return false;
}
}
return true;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Iterator;
use Symfony\Component\Finder\Glob;
/**
* FilenameFilterIterator filters files by patterns (a regexp, a glob, or a string).
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class FilenameFilterIterator extends MultiplePcreFilterIterator
{
/**
* Filters the iterator values.
*
* @return bool true if the value should be kept, false otherwise
*/
public function accept()
{
return $this->isAccepted($this->current()->getFilename());
}
/**
* Converts glob to regexp.
*
* PCRE patterns are left unchanged.
* Glob strings are transformed with Glob::toRegex().
*
* @param string $str Pattern: glob or regexp
*
* @return string regexp corresponding to a given glob or regexp
*/
protected function toRegex(string $str)
{
return $this->isRegex($str) ? $str : Glob::toRegex($str);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Iterator;
/**
* FilecontentFilterIterator filters files by their contents using patterns (regexps or strings).
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Włodzimierz Gajda <gajdaw@gajdaw.pl>
*/
class FilecontentFilterIterator extends MultiplePcreFilterIterator
{
/**
* Filters the iterator values.
*
* @return bool true if the value should be kept, false otherwise
*/
public function accept()
{
if (!$this->matchRegexps && !$this->noMatchRegexps) {
return true;
}
$fileinfo = $this->current();
if ($fileinfo->isDir() || !$fileinfo->isReadable()) {
return false;
}
$content = $fileinfo->getContents();
if (!$content) {
return false;
}
return $this->isAccepted($content);
}
/**
* Converts string to regexp if necessary.
*
* @param string $str Pattern: string or regexp
*
* @return string regexp corresponding to a given string or regexp
*/
protected function toRegex(string $str)
{
return $this->isRegex($str) ? $str : '/'.preg_quote($str, '/').'/';
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Iterator;
use Symfony\Component\Finder\Comparator\DateComparator;
/**
* DateRangeFilterIterator filters out files that are not in the given date range (last modified dates).
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class DateRangeFilterIterator extends \FilterIterator
{
private $comparators = [];
/**
* @param \Iterator $iterator The Iterator to filter
* @param DateComparator[] $comparators An array of DateComparator instances
*/
public function __construct(\Iterator $iterator, array $comparators)
{
$this->comparators = $comparators;
parent::__construct($iterator);
}
/**
* Filters the iterator values.
*
* @return bool true if the value should be kept, false otherwise
*/
public function accept()
{
$fileinfo = $this->current();
if (!file_exists($fileinfo->getPathname())) {
return false;
}
$filedate = $fileinfo->getMTime();
foreach ($this->comparators as $compare) {
if (!$compare->test($filedate)) {
return false;
}
}
return true;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Iterator;
/**
* PathFilterIterator filters files by path patterns (e.g. some/special/dir).
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Włodzimierz Gajda <gajdaw@gajdaw.pl>
*/
class PathFilterIterator extends MultiplePcreFilterIterator
{
/**
* Filters the iterator values.
*
* @return bool true if the value should be kept, false otherwise
*/
public function accept()
{
$filename = $this->current()->getRelativePathname();
if ('\\' === \DIRECTORY_SEPARATOR) {
$filename = str_replace('\\', '/', $filename);
}
return $this->isAccepted($filename);
}
/**
* Converts strings to regexp.
*
* PCRE patterns are left unchanged.
*
* Default conversion:
* 'lorem/ipsum/dolor' ==> 'lorem\/ipsum\/dolor/'
*
* Use only / as directory separator (on Windows also).
*
* @param string $str Pattern: regexp or dirname
*
* @return string regexp corresponding to a given string or regexp
*/
protected function toRegex(string $str)
{
return $this->isRegex($str) ? $str : '/'.preg_quote($str, '/').'/';
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Iterator;
/**
* ExcludeDirectoryFilterIterator filters out directories.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class ExcludeDirectoryFilterIterator extends \FilterIterator implements \RecursiveIterator
{
private $iterator;
private $isRecursive;
private $excludedDirs = [];
private $excludedPattern;
/**
* @param \Iterator $iterator The Iterator to filter
* @param string[] $directories An array of directories to exclude
*/
public function __construct(\Iterator $iterator, array $directories)
{
$this->iterator = $iterator;
$this->isRecursive = $iterator instanceof \RecursiveIterator;
$patterns = [];
foreach ($directories as $directory) {
$directory = rtrim($directory, '/');
if (!$this->isRecursive || false !== strpos($directory, '/')) {
$patterns[] = preg_quote($directory, '#');
} else {
$this->excludedDirs[$directory] = true;
}
}
if ($patterns) {
$this->excludedPattern = '#(?:^|/)(?:'.implode('|', $patterns).')(?:/|$)#';
}
parent::__construct($iterator);
}
/**
* Filters the iterator values.
*
* @return bool True if the value should be kept, false otherwise
*/
public function accept()
{
if ($this->isRecursive && isset($this->excludedDirs[$this->getFilename()]) && $this->isDir()) {
return false;
}
if ($this->excludedPattern) {
$path = $this->isDir() ? $this->current()->getRelativePathname() : $this->current()->getRelativePath();
$path = str_replace('\\', '/', $path);
return !preg_match($this->excludedPattern, $path);
}
return true;
}
/**
* @return bool
*/
public function hasChildren()
{
return $this->isRecursive && $this->iterator->hasChildren();
}
public function getChildren()
{
$children = new self($this->iterator->getChildren(), []);
$children->excludedDirs = $this->excludedDirs;
$children->excludedPattern = $this->excludedPattern;
return $children;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Iterator;
/**
* MultiplePcreFilterIterator filters files using patterns (regexps, globs or strings).
*
* @author Fabien Potencier <fabien@symfony.com>
*/
abstract class MultiplePcreFilterIterator extends \FilterIterator
{
protected $matchRegexps = [];
protected $noMatchRegexps = [];
/**
* @param \Iterator $iterator The Iterator to filter
* @param string[] $matchPatterns An array of patterns that need to match
* @param string[] $noMatchPatterns An array of patterns that need to not match
*/
public function __construct(\Iterator $iterator, array $matchPatterns, array $noMatchPatterns)
{
foreach ($matchPatterns as $pattern) {
$this->matchRegexps[] = $this->toRegex($pattern);
}
foreach ($noMatchPatterns as $pattern) {
$this->noMatchRegexps[] = $this->toRegex($pattern);
}
parent::__construct($iterator);
}
/**
* Checks whether the string is accepted by the regex filters.
*
* If there is no regexps defined in the class, this method will accept the string.
* Such case can be handled by child classes before calling the method if they want to
* apply a different behavior.
*
* @return bool
*/
protected function isAccepted(string $string)
{
// should at least not match one rule to exclude
foreach ($this->noMatchRegexps as $regex) {
if (preg_match($regex, $string)) {
return false;
}
}
// should at least match one rule
if ($this->matchRegexps) {
foreach ($this->matchRegexps as $regex) {
if (preg_match($regex, $string)) {
return true;
}
}
return false;
}
// If there is no match rules, the file is accepted
return true;
}
/**
* Checks whether the string is a regex.
*
* @return bool
*/
protected function isRegex(string $str)
{
if (preg_match('/^(.{3,}?)[imsxuADU]*$/', $str, $m)) {
$start = substr($m[1], 0, 1);
$end = substr($m[1], -1);
if ($start === $end) {
return !preg_match('/[*?[:alnum:] \\\\]/', $start);
}
foreach ([['{', '}'], ['(', ')'], ['[', ']'], ['<', '>']] as $delimiters) {
if ($start === $delimiters[0] && $end === $delimiters[1]) {
return true;
}
}
}
return false;
}
/**
* Converts string into regexp.
*
* @return string
*/
abstract protected function toRegex(string $str);
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Comparator;
/**
* NumberComparator compiles a simple comparison to an anonymous
* subroutine, which you can call with a value to be tested again.
*
* Now this would be very pointless, if NumberCompare didn't understand
* magnitudes.
*
* The target value may use magnitudes of kilobytes (k, ki),
* megabytes (m, mi), or gigabytes (g, gi). Those suffixed
* with an i use the appropriate 2**n version in accordance with the
* IEC standard: http://physics.nist.gov/cuu/Units/binary.html
*
* Based on the Perl Number::Compare module.
*
* @author Fabien Potencier <fabien@symfony.com> PHP port
* @author Richard Clamp <richardc@unixbeard.net> Perl version
* @copyright 2004-2005 Fabien Potencier <fabien@symfony.com>
* @copyright 2002 Richard Clamp <richardc@unixbeard.net>
*
* @see http://physics.nist.gov/cuu/Units/binary.html
*/
class NumberComparator extends Comparator
{
/**
* @param string|int $test A comparison string or an integer
*
* @throws \InvalidArgumentException If the test is not understood
*/
public function __construct(?string $test)
{
if (!preg_match('#^\s*(==|!=|[<>]=?)?\s*([0-9\.]+)\s*([kmg]i?)?\s*$#i', $test, $matches)) {
throw new \InvalidArgumentException(sprintf('Don\'t understand "%s" as a number test.', $test));
}
$target = $matches[2];
if (!is_numeric($target)) {
throw new \InvalidArgumentException(sprintf('Invalid number "%s".', $target));
}
if (isset($matches[3])) {
// magnitude
switch (strtolower($matches[3])) {
case 'k':
$target *= 1000;
break;
case 'ki':
$target *= 1024;
break;
case 'm':
$target *= 1000000;
break;
case 'mi':
$target *= 1024 * 1024;
break;
case 'g':
$target *= 1000000000;
break;
case 'gi':
$target *= 1024 * 1024 * 1024;
break;
}
}
$this->setTarget($target);
$this->setOperator(isset($matches[1]) ? $matches[1] : '==');
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Comparator;
/**
* DateCompare compiles date comparisons.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class DateComparator extends Comparator
{
/**
* @param string $test A comparison string
*
* @throws \InvalidArgumentException If the test is not understood
*/
public function __construct(string $test)
{
if (!preg_match('#^\s*(==|!=|[<>]=?|after|since|before|until)?\s*(.+?)\s*$#i', $test, $matches)) {
throw new \InvalidArgumentException(sprintf('Don\'t understand "%s" as a date test.', $test));
}
try {
$date = new \DateTime($matches[2]);
$target = $date->format('U');
} catch (\Exception $e) {
throw new \InvalidArgumentException(sprintf('"%s" is not a valid date.', $matches[2]));
}
$operator = isset($matches[1]) ? $matches[1] : '==';
if ('since' === $operator || 'after' === $operator) {
$operator = '>';
}
if ('until' === $operator || 'before' === $operator) {
$operator = '<';
}
$this->setOperator($operator);
$this->setTarget($target);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Comparator;
/**
* Comparator.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class Comparator
{
private $target;
private $operator = '==';
/**
* Gets the target value.
*
* @return string The target value
*/
public function getTarget()
{
return $this->target;
}
public function setTarget(string $target)
{
$this->target = $target;
}
/**
* Gets the comparison operator.
*
* @return string The operator
*/
public function getOperator()
{
return $this->operator;
}
/**
* Sets the comparison operator.
*
* @throws \InvalidArgumentException
*/
public function setOperator(string $operator)
{
if ('' === $operator) {
$operator = '==';
}
if (!\in_array($operator, ['>', '<', '>=', '<=', '==', '!='])) {
throw new \InvalidArgumentException(sprintf('Invalid operator "%s".', $operator));
}
$this->operator = $operator;
}
/**
* Tests against the target.
*
* @param mixed $test A test value
*
* @return bool
*/
public function test($test)
{
switch ($this->operator) {
case '>':
return $test > $this->target;
case '>=':
return $test >= $this->target;
case '<':
return $test < $this->target;
case '<=':
return $test <= $this->target;
case '!=':
return $test != $this->target;
}
return $test == $this->target;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder;
/**
* Gitignore matches against text.
*
* @author Ahmed Abdou <mail@ahmd.io>
*/
class Gitignore
{
/**
* Returns a regexp which is the equivalent of the gitignore pattern.
*
* @return string The regexp
*/
public static function toRegex(string $gitignoreFileContent): string
{
$gitignoreFileContent = preg_replace('/^[^\\\r\n]*#.*/m', '', $gitignoreFileContent);
$gitignoreLines = preg_split('/\r\n|\r|\n/', $gitignoreFileContent);
$positives = [];
$negatives = [];
foreach ($gitignoreLines as $i => $line) {
$line = trim($line);
if ('' === $line) {
continue;
}
if (1 === preg_match('/^!/', $line)) {
$positives[$i] = null;
$negatives[$i] = self::getRegexFromGitignore(preg_replace('/^!(.*)/', '${1}', $line), true);
continue;
}
$negatives[$i] = null;
$positives[$i] = self::getRegexFromGitignore($line);
}
$index = 0;
$patterns = [];
foreach ($positives as $pattern) {
if (null === $pattern) {
continue;
}
$negativesAfter = array_filter(\array_slice($negatives, ++$index));
if ([] !== $negativesAfter) {
$pattern .= sprintf('(?<!%s)', implode('|', $negativesAfter));
}
$patterns[] = $pattern;
}
return sprintf('/^((%s))$/', implode(')|(', $patterns));
}
private static function getRegexFromGitignore(string $gitignorePattern, bool $negative = false): string
{
$regex = '';
$isRelativePath = false;
// If there is a separator at the beginning or middle (or both) of the pattern, then the pattern is relative to the directory level of the particular .gitignore file itself
$slashPosition = strpos($gitignorePattern, '/');
if (false !== $slashPosition && \strlen($gitignorePattern) - 1 !== $slashPosition) {
if (0 === $slashPosition) {
$gitignorePattern = substr($gitignorePattern, 1);
}
$isRelativePath = true;
$regex .= '^';
}
if ('/' === $gitignorePattern[\strlen($gitignorePattern) - 1]) {
$gitignorePattern = substr($gitignorePattern, 0, -1);
}
$iMax = \strlen($gitignorePattern);
for ($i = 0; $i < $iMax; ++$i) {
$tripleChars = substr($gitignorePattern, $i, 3);
if ('**/' === $tripleChars || '/**' === $tripleChars) {
$regex .= '.*';
$i += 2;
continue;
}
$doubleChars = substr($gitignorePattern, $i, 2);
if ('**' === $doubleChars) {
$regex .= '.*';
++$i;
continue;
}
if ('*/' === $doubleChars) {
$regex .= '[^\/]*\/?[^\/]*';
++$i;
continue;
}
$c = $gitignorePattern[$i];
switch ($c) {
case '*':
$regex .= $isRelativePath ? '[^\/]*' : '[^\/]*\/?[^\/]*';
break;
case '/':
case '.':
case ':':
case '(':
case ')':
case '{':
case '}':
$regex .= '\\'.$c;
break;
default:
$regex .= $c;
}
}
if ($negative) {
// a lookbehind assertion has to be a fixed width (it can not have nested '|' statements)
return sprintf('%s$|%s\/$', $regex, $regex);
}
return '(?>'.$regex.'($|\/.*))';
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Symfony\Polyfill\Ctype as p;
if (!function_exists('ctype_alnum')) {
function ctype_alnum($input) { return p\Ctype::ctype_alnum($input); }
}
if (!function_exists('ctype_alpha')) {
function ctype_alpha($input) { return p\Ctype::ctype_alpha($input); }
}
if (!function_exists('ctype_cntrl')) {
function ctype_cntrl($input) { return p\Ctype::ctype_cntrl($input); }
}
if (!function_exists('ctype_digit')) {
function ctype_digit($input) { return p\Ctype::ctype_digit($input); }
}
if (!function_exists('ctype_graph')) {
function ctype_graph($input) { return p\Ctype::ctype_graph($input); }
}
if (!function_exists('ctype_lower')) {
function ctype_lower($input) { return p\Ctype::ctype_lower($input); }
}
if (!function_exists('ctype_print')) {
function ctype_print($input) { return p\Ctype::ctype_print($input); }
}
if (!function_exists('ctype_punct')) {
function ctype_punct($input) { return p\Ctype::ctype_punct($input); }
}
if (!function_exists('ctype_space')) {
function ctype_space($input) { return p\Ctype::ctype_space($input); }
}
if (!function_exists('ctype_upper')) {
function ctype_upper($input) { return p\Ctype::ctype_upper($input); }
}
if (!function_exists('ctype_xdigit')) {
function ctype_xdigit($input) { return p\Ctype::ctype_xdigit($input); }
}
Copyright (c) 2018-2019 Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Polyfill\Ctype;
/**
* Ctype implementation through regex.
*
* @internal
*
* @author Gert de Pagter <BackEndTea@gmail.com>
*/
final class Ctype
{
/**
* Returns TRUE if every character in text is either a letter or a digit, FALSE otherwise.
*
* @see https://php.net/ctype-alnum
*
* @param string|int $text
*
* @return bool
*/
public static function ctype_alnum($text)
{
$text = self::convert_int_to_char_for_ctype($text);
return \is_string($text) && '' !== $text && !preg_match('/[^A-Za-z0-9]/', $text);
}
/**
* Returns TRUE if every character in text is a letter, FALSE otherwise.
*
* @see https://php.net/ctype-alpha
*
* @param string|int $text
*
* @return bool
*/
public static function ctype_alpha($text)
{
$text = self::convert_int_to_char_for_ctype($text);
return \is_string($text) && '' !== $text && !preg_match('/[^A-Za-z]/', $text);
}
/**
* Returns TRUE if every character in text is a control character from the current locale, FALSE otherwise.
*
* @see https://php.net/ctype-cntrl
*
* @param string|int $text
*
* @return bool
*/
public static function ctype_cntrl($text)
{
$text = self::convert_int_to_char_for_ctype($text);
return \is_string($text) && '' !== $text && !preg_match('/[^\x00-\x1f\x7f]/', $text);
}
/**
* Returns TRUE if every character in the string text is a decimal digit, FALSE otherwise.
*
* @see https://php.net/ctype-digit
*
* @param string|int $text
*
* @return bool
*/
public static function ctype_digit($text)
{
$text = self::convert_int_to_char_for_ctype($text);
return \is_string($text) && '' !== $text && !preg_match('/[^0-9]/', $text);
}
/**
* Returns TRUE if every character in text is printable and actually creates visible output (no white space), FALSE otherwise.
*
* @see https://php.net/ctype-graph
*
* @param string|int $text
*
* @return bool
*/
public static function ctype_graph($text)
{
$text = self::convert_int_to_char_for_ctype($text);
return \is_string($text) && '' !== $text && !preg_match('/[^!-~]/', $text);
}
/**
* Returns TRUE if every character in text is a lowercase letter.
*
* @see https://php.net/ctype-lower
*
* @param string|int $text
*
* @return bool
*/
public static function ctype_lower($text)
{
$text = self::convert_int_to_char_for_ctype($text);
return \is_string($text) && '' !== $text && !preg_match('/[^a-z]/', $text);
}
/**
* Returns TRUE if every character in text will actually create output (including blanks). Returns FALSE if text contains control characters or characters that do not have any output or control function at all.
*
* @see https://php.net/ctype-print
*
* @param string|int $text
*
* @return bool
*/
public static function ctype_print($text)
{
$text = self::convert_int_to_char_for_ctype($text);
return \is_string($text) && '' !== $text && !preg_match('/[^ -~]/', $text);
}
/**
* Returns TRUE if every character in text is printable, but neither letter, digit or blank, FALSE otherwise.
*
* @see https://php.net/ctype-punct
*
* @param string|int $text
*
* @return bool
*/
public static function ctype_punct($text)
{
$text = self::convert_int_to_char_for_ctype($text);
return \is_string($text) && '' !== $text && !preg_match('/[^!-\/\:-@\[-`\{-~]/', $text);
}
/**
* Returns TRUE if every character in text creates some sort of white space, FALSE otherwise. Besides the blank character this also includes tab, vertical tab, line feed, carriage return and form feed characters.
*
* @see https://php.net/ctype-space
*
* @param string|int $text
*
* @return bool
*/
public static function ctype_space($text)
{
$text = self::convert_int_to_char_for_ctype($text);
return \is_string($text) && '' !== $text && !preg_match('/[^\s]/', $text);
}
/**
* Returns TRUE if every character in text is an uppercase letter.
*
* @see https://php.net/ctype-upper
*
* @param string|int $text
*
* @return bool
*/
public static function ctype_upper($text)
{
$text = self::convert_int_to_char_for_ctype($text);
return \is_string($text) && '' !== $text && !preg_match('/[^A-Z]/', $text);
}
/**
* Returns TRUE if every character in text is a hexadecimal 'digit', that is a decimal digit or a character from [A-Fa-f] , FALSE otherwise.
*
* @see https://php.net/ctype-xdigit
*
* @param string|int $text
*
* @return bool
*/
public static function ctype_xdigit($text)
{
$text = self::convert_int_to_char_for_ctype($text);
return \is_string($text) && '' !== $text && !preg_match('/[^A-Fa-f0-9]/', $text);
}
/**
* Converts integers to their char versions according to normal ctype behaviour, if needed.
*
* If an integer between -128 and 255 inclusive is provided,
* it is interpreted as the ASCII value of a single character
* (negative values have 256 added in order to allow characters in the Extended ASCII range).
* Any other integer is interpreted as a string containing the decimal digits of the integer.
*
* @param string|int $int
*
* @return mixed
*/
private static function convert_int_to_char_for_ctype($int)
{
if (!\is_int($int)) {
return $int;
}
if ($int < -128 || $int > 255) {
return (string) $int;
}
if ($int < 0) {
$int += 256;
}
return \chr($int);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Symfony\Polyfill\Intl\Grapheme as p;
if (extension_loaded('intl')) {
return;
}
if (!defined('GRAPHEME_EXTR_COUNT')) {
define('GRAPHEME_EXTR_COUNT', 0);
}
if (!defined('GRAPHEME_EXTR_MAXBYTES')) {
define('GRAPHEME_EXTR_MAXBYTES', 1);
}
if (!defined('GRAPHEME_EXTR_MAXCHARS')) {
define('GRAPHEME_EXTR_MAXCHARS', 2);
}
if (!function_exists('grapheme_extract')) {
function grapheme_extract($haystack, $size, $extract_type = 0, $start = 0, &$next = 0) { return p\Grapheme::grapheme_extract($haystack, $size, $extract_type, $start, $next); }
}
if (!function_exists('grapheme_stripos')) {
function grapheme_stripos($haystack, $needle, $offset = 0) { return p\Grapheme::grapheme_stripos($haystack, $needle, $offset); }
}
if (!function_exists('grapheme_stristr')) {
function grapheme_stristr($haystack, $needle, $before_needle = false) { return p\Grapheme::grapheme_stristr($haystack, $needle, $before_needle); }
}
if (!function_exists('grapheme_strlen')) {
function grapheme_strlen($input) { return p\Grapheme::grapheme_strlen($input); }
}
if (!function_exists('grapheme_strpos')) {
function grapheme_strpos($haystack, $needle, $offset = 0) { return p\Grapheme::grapheme_strpos($haystack, $needle, $offset); }
}
if (!function_exists('grapheme_strripos')) {
function grapheme_strripos($haystack, $needle, $offset = 0) { return p\Grapheme::grapheme_strripos($haystack, $needle, $offset); }
}
if (!function_exists('grapheme_strrpos')) {
function grapheme_strrpos($haystack, $needle, $offset = 0) { return p\Grapheme::grapheme_strrpos($haystack, $needle, $offset); }
}
if (!function_exists('grapheme_strstr')) {
function grapheme_strstr($haystack, $needle, $before_needle = false) { return p\Grapheme::grapheme_strstr($haystack, $needle, $before_needle); }
}
if (!function_exists('grapheme_substr')) {
function grapheme_substr($string, $start, $length = null) { return p\Grapheme::grapheme_substr($string, $start, $length); }
}
Copyright (c) 2015-2019 Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Polyfill\Intl\Grapheme;
\define('SYMFONY_GRAPHEME_CLUSTER_RX', PCRE_VERSION >= '8.32' ? '\X' : Grapheme::GRAPHEME_CLUSTER_RX);
/**
* Partial intl implementation in pure PHP.
*
* Implemented:
* - grapheme_extract - Extract a sequence of grapheme clusters from a text buffer, which must be encoded in UTF-8
* - grapheme_stripos - Find position (in grapheme units) of first occurrence of a case-insensitive string
* - grapheme_stristr - Returns part of haystack string from the first occurrence of case-insensitive needle to the end of haystack
* - grapheme_strlen - Get string length in grapheme units
* - grapheme_strpos - Find position (in grapheme units) of first occurrence of a string
* - grapheme_strripos - Find position (in grapheme units) of last occurrence of a case-insensitive string
* - grapheme_strrpos - Find position (in grapheme units) of last occurrence of a string
* - grapheme_strstr - Returns part of haystack string from the first occurrence of needle to the end of haystack
* - grapheme_substr - Return part of a string
*
* @author Nicolas Grekas <p@tchwork.com>
*
* @internal
*/
final class Grapheme
{
// (CRLF|([ZWNJ-ZWJ]|T+|L*(LV?V+|LV|LVT)T*|L+|[^Control])[Extend]*|[Control])
// This regular expression is a work around for http://bugs.exim.org/1279
const GRAPHEME_CLUSTER_RX = '(?:\r\n|(?:[ -~\x{200C}\x{200D}]|[ᆨ-ᇹ]+|[ᄀ-]*(?:[가개갸걔거게겨계고과괘괴교구궈궤귀규그긔기까깨꺄꺠꺼께껴꼐꼬꽈꽤꾀꾜꾸꿔꿰뀌뀨끄끠끼나내냐냬너네녀녜노놔놰뇌뇨누눠눼뉘뉴느늬니다대댜댸더데뎌뎨도돠돼되됴두둬뒈뒤듀드듸디따때땨떄떠떼뗘뗴또똬뙈뙤뚀뚜뚸뛔뛰뜌뜨띄띠라래랴럐러레려례로롸뢔뢰료루뤄뤠뤼류르릐리마매먀먜머메며몌모뫄뫠뫼묘무뭐뭬뮈뮤므믜미바배뱌뱨버베벼볘보봐봬뵈뵤부붜붸뷔뷰브븨비빠빼뺘뺴뻐뻬뼈뼤뽀뽜뽸뾔뾰뿌뿨쀄쀠쀼쁘쁴삐사새샤섀서세셔셰소솨쇄쇠쇼수숴쉐쉬슈스싀시싸쌔쌰썌써쎄쎠쎼쏘쏴쐐쐬쑈쑤쒀쒜쒸쓔쓰씌씨아애야얘어에여예오와왜외요우워웨위유으의이자재쟈쟤저제져졔조좌좨죄죠주줘줴쥐쥬즈즤지짜째쨔쨰쩌쩨쪄쪠쪼쫘쫴쬐쬬쭈쭤쮀쮜쮸쯔쯰찌차채챠챼처체쳐쳬초촤쵀최쵸추춰췌취츄츠츼치카캐캬컈커케켜켸코콰쾌쾨쿄쿠쿼퀘퀴큐크킈키타태탸턔터테텨톄토톼퇘퇴툐투퉈퉤튀튜트틔티파패퍄퍠퍼페펴폐포퐈퐤푀표푸풔풰퓌퓨프픠피하해햐햬허헤혀혜호화홰회효후훠훼휘휴흐희히]?[-ᆢ]+|[가-힣])[ᆨ-ᇹ]*|[ᄀ-]+|[^\p{Cc}\p{Cf}\p{Zl}\p{Zp}])[\p{Mn}\p{Me}\x{09BE}\x{09D7}\x{0B3E}\x{0B57}\x{0BBE}\x{0BD7}\x{0CC2}\x{0CD5}\x{0CD6}\x{0D3E}\x{0D57}\x{0DCF}\x{0DDF}\x{200C}\x{200D}\x{1D165}\x{1D16E}-\x{1D172}]*|[\p{Cc}\p{Cf}\p{Zl}\p{Zp}])';
public static function grapheme_extract($s, $size, $type = GRAPHEME_EXTR_COUNT, $start = 0, &$next = 0)
{
if (0 > $start) {
$start = \strlen($s) + $start;
}
if (!\is_scalar($s)) {
$hasError = false;
set_error_handler(function () use (&$hasError) { $hasError = true; });
$next = substr($s, $start);
restore_error_handler();
if ($hasError) {
substr($s, $start);
$s = '';
} else {
$s = $next;
}
} else {
$s = substr($s, $start);
}
$size = (int) $size;
$type = (int) $type;
$start = (int) $start;
if (!isset($s[0]) || 0 > $size || 0 > $start || 0 > $type || 2 < $type) {
return false;
}
if (0 === $size) {
return '';
}
$next = $start;
$s = preg_split('/('.SYMFONY_GRAPHEME_CLUSTER_RX.')/u', "\r\n".$s, $size + 1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
if (!isset($s[1])) {
return false;
}
$i = 1;
$ret = '';
do {
if (GRAPHEME_EXTR_COUNT === $type) {
--$size;
} elseif (GRAPHEME_EXTR_MAXBYTES === $type) {
$size -= \strlen($s[$i]);
} else {
$size -= iconv_strlen($s[$i], 'UTF-8//IGNORE');
}
if ($size >= 0) {
$ret .= $s[$i];
}
} while (isset($s[++$i]) && $size > 0);
$next += \strlen($ret);
return $ret;
}
public static function grapheme_strlen($s)
{
preg_replace('/'.SYMFONY_GRAPHEME_CLUSTER_RX.'/u', '', $s, -1, $len);
return 0 === $len && '' !== $s ? null : $len;
}
public static function grapheme_substr($s, $start, $len = null)
{
if (null === $len) {
$len = 2147483647;
}
preg_match_all('/'.SYMFONY_GRAPHEME_CLUSTER_RX.'/u', $s, $s);
$slen = \count($s[0]);
$start = (int) $start;
if (0 > $start) {
$start += $slen;
}
if (0 > $start) {
if (\PHP_VERSION_ID < 80000) {
return false;
}
$start = 0;
}
if ($start >= $slen) {
return \PHP_VERSION_ID >= 80000 ? '' : false;
}
$rem = $slen - $start;
if (0 > $len) {
$len += $rem;
}
if (0 === $len) {
return '';
}
if (0 > $len) {
return \PHP_VERSION_ID >= 80000 ? '' : false;
}
if ($len > $rem) {
$len = $rem;
}
return implode('', \array_slice($s[0], $start, $len));
}
public static function grapheme_strpos($s, $needle, $offset = 0)
{
return self::grapheme_position($s, $needle, $offset, 0);
}
public static function grapheme_stripos($s, $needle, $offset = 0)
{
return self::grapheme_position($s, $needle, $offset, 1);
}
public static function grapheme_strrpos($s, $needle, $offset = 0)
{
return self::grapheme_position($s, $needle, $offset, 2);
}
public static function grapheme_strripos($s, $needle, $offset = 0)
{
return self::grapheme_position($s, $needle, $offset, 3);
}
public static function grapheme_stristr($s, $needle, $beforeNeedle = false)
{
return mb_stristr($s, $needle, $beforeNeedle, 'UTF-8');
}
public static function grapheme_strstr($s, $needle, $beforeNeedle = false)
{
return mb_strstr($s, $needle, $beforeNeedle, 'UTF-8');
}
private static function grapheme_position($s, $needle, $offset, $mode)
{
$needle = (string) $needle;
if (!preg_match('/./us', $needle)) {
return false;
}
$s = (string) $s;
if (!preg_match('/./us', $s)) {
return false;
}
if ($offset > 0) {
$s = self::grapheme_substr($s, $offset);
} elseif ($offset < 0) {
if (2 > $mode) {
$offset += self::grapheme_strlen($s);
$s = self::grapheme_substr($s, $offset);
if (0 > $offset) {
$offset = 0;
}
} elseif (0 > $offset += self::grapheme_strlen($needle)) {
$s = self::grapheme_substr($s, 0, $offset);
$offset = 0;
} else {
$offset = 0;
}
}
switch ($mode) {
case 0: $needle = iconv_strpos($s, $needle, 0, 'UTF-8'); break;
case 1: $needle = mb_stripos($s, $needle, 0, 'UTF-8'); break;
case 2: $needle = iconv_strrpos($s, $needle, 'UTF-8'); break;
default: $needle = mb_strripos($s, $needle, 0, 'UTF-8'); break;
}
return false !== $needle ? self::grapheme_strlen(iconv_substr($s, 0, $needle, 'UTF-8')) + $offset : false;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Symfony\Polyfill\Intl\Normalizer as p;
if (!function_exists('normalizer_is_normalized')) {
function normalizer_is_normalized($input, $form = p\Normalizer::NFC) { return p\Normalizer::isNormalized($input, $form); }
}
if (!function_exists('normalizer_normalize')) {
function normalizer_normalize($input, $form = p\Normalizer::NFC) { return p\Normalizer::normalize($input, $form); }
}
Copyright (c) 2015-2019 Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Polyfill\Intl\Normalizer;
/**
* Normalizer is a PHP fallback implementation of the Normalizer class provided by the intl extension.
*
* It has been validated with Unicode 6.3 Normalization Conformance Test.
* See http://www.unicode.org/reports/tr15/ for detailed info about Unicode normalizations.
*
* @author Nicolas Grekas <p@tchwork.com>
*
* @internal
*/
class Normalizer
{
const FORM_D = \Normalizer::FORM_D;
const FORM_KD = \Normalizer::FORM_KD;
const FORM_C = \Normalizer::FORM_C;
const FORM_KC = \Normalizer::FORM_KC;
const NFD = \Normalizer::NFD;
const NFKD = \Normalizer::NFKD;
const NFC = \Normalizer::NFC;
const NFKC = \Normalizer::NFKC;
private static $C;
private static $D;
private static $KD;
private static $cC;
private static $ulenMask = array("\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4);
private static $ASCII = "\x20\x65\x69\x61\x73\x6E\x74\x72\x6F\x6C\x75\x64\x5D\x5B\x63\x6D\x70\x27\x0A\x67\x7C\x68\x76\x2E\x66\x62\x2C\x3A\x3D\x2D\x71\x31\x30\x43\x32\x2A\x79\x78\x29\x28\x4C\x39\x41\x53\x2F\x50\x22\x45\x6A\x4D\x49\x6B\x33\x3E\x35\x54\x3C\x44\x34\x7D\x42\x7B\x38\x46\x77\x52\x36\x37\x55\x47\x4E\x3B\x4A\x7A\x56\x23\x48\x4F\x57\x5F\x26\x21\x4B\x3F\x58\x51\x25\x59\x5C\x09\x5A\x2B\x7E\x5E\x24\x40\x60\x7F\x00\x01\x02\x03\x04\x05\x06\x07\x08\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F";
public static function isNormalized($s, $form = self::NFC)
{
if (!\in_array($form, array(self::NFD, self::NFKD, self::NFC, self::NFKC))) {
return false;
}
$s = (string) $s;
if (!isset($s[strspn($s, self::$ASCII)])) {
return true;
}
if (self::NFC == $form && preg_match('//u', $s) && !preg_match('/[^\x00-\x{2FF}]/u', $s)) {
return true;
}
return self::normalize($s, $form) === $s;
}
public static function normalize($s, $form = self::NFC)
{
$s = (string) $s;
if (!preg_match('//u', $s)) {
return false;
}
switch ($form) {
case self::NFC: $C = true; $K = false; break;
case self::NFD: $C = false; $K = false; break;
case self::NFKC: $C = true; $K = true; break;
case self::NFKD: $C = false; $K = true; break;
default:
if (\defined('Normalizer::NONE') && \Normalizer::NONE == $form) {
return $s;
}
return false;
}
if ('' === $s) {
return '';
}
if ($K && null === self::$KD) {
self::$KD = self::getData('compatibilityDecomposition');
}
if (null === self::$D) {
self::$D = self::getData('canonicalDecomposition');
self::$cC = self::getData('combiningClass');
}
if (null !== $mbEncoding = (2 /* MB_OVERLOAD_STRING */ & (int) ini_get('mbstring.func_overload')) ? mb_internal_encoding() : null) {
mb_internal_encoding('8bit');
}
$r = self::decompose($s, $K);
if ($C) {
if (null === self::$C) {
self::$C = self::getData('canonicalComposition');
}
$r = self::recompose($r);
}
if (null !== $mbEncoding) {
mb_internal_encoding($mbEncoding);
}
return $r;
}
private static function recompose($s)
{
$ASCII = self::$ASCII;
$compMap = self::$C;
$combClass = self::$cC;
$ulenMask = self::$ulenMask;
$result = $tail = '';
$i = $s[0] < "\x80" ? 1 : $ulenMask[$s[0] & "\xF0"];
$len = \strlen($s);
$lastUchr = substr($s, 0, $i);
$lastUcls = isset($combClass[$lastUchr]) ? 256 : 0;
while ($i < $len) {
if ($s[$i] < "\x80") {
// ASCII chars
if ($tail) {
$lastUchr .= $tail;
$tail = '';
}
if ($j = strspn($s, $ASCII, $i + 1)) {
$lastUchr .= substr($s, $i, $j);
$i += $j;
}
$result .= $lastUchr;
$lastUchr = $s[$i];
$lastUcls = 0;
++$i;
continue;
}
$ulen = $ulenMask[$s[$i] & "\xF0"];
$uchr = substr($s, $i, $ulen);
if ($lastUchr < "\xE1\x84\x80" || "\xE1\x84\x92" < $lastUchr
|| $uchr < "\xE1\x85\xA1" || "\xE1\x85\xB5" < $uchr
|| $lastUcls) {
// Table lookup and combining chars composition
$ucls = isset($combClass[$uchr]) ? $combClass[$uchr] : 0;
if (isset($compMap[$lastUchr.$uchr]) && (!$lastUcls || $lastUcls < $ucls)) {
$lastUchr = $compMap[$lastUchr.$uchr];
} elseif ($lastUcls = $ucls) {
$tail .= $uchr;
} else {
if ($tail) {
$lastUchr .= $tail;
$tail = '';
}
$result .= $lastUchr;
$lastUchr = $uchr;
}
} else {
// Hangul chars
$L = \ord($lastUchr[2]) - 0x80;
$V = \ord($uchr[2]) - 0xA1;
$T = 0;
$uchr = substr($s, $i + $ulen, 3);
if ("\xE1\x86\xA7" <= $uchr && $uchr <= "\xE1\x87\x82") {
$T = \ord($uchr[2]) - 0xA7;
0 > $T && $T += 0x40;
$ulen += 3;
}
$L = 0xAC00 + ($L * 21 + $V) * 28 + $T;
$lastUchr = \chr(0xE0 | $L >> 12).\chr(0x80 | $L >> 6 & 0x3F).\chr(0x80 | $L & 0x3F);
}
$i += $ulen;
}
return $result.$lastUchr.$tail;
}
private static function decompose($s, $c)
{
$result = '';
$ASCII = self::$ASCII;
$decompMap = self::$D;
$combClass = self::$cC;
$ulenMask = self::$ulenMask;
if ($c) {
$compatMap = self::$KD;
}
$c = array();
$i = 0;
$len = \strlen($s);
while ($i < $len) {
if ($s[$i] < "\x80") {
// ASCII chars
if ($c) {
ksort($c);
$result .= implode('', $c);
$c = array();
}
$j = 1 + strspn($s, $ASCII, $i + 1);
$result .= substr($s, $i, $j);
$i += $j;
continue;
}
$ulen = $ulenMask[$s[$i] & "\xF0"];
$uchr = substr($s, $i, $ulen);
$i += $ulen;
if ($uchr < "\xEA\xB0\x80" || "\xED\x9E\xA3" < $uchr) {
// Table lookup
if ($uchr !== $j = isset($compatMap[$uchr]) ? $compatMap[$uchr] : (isset($decompMap[$uchr]) ? $decompMap[$uchr] : $uchr)) {
$uchr = $j;
$j = \strlen($uchr);
$ulen = $uchr[0] < "\x80" ? 1 : $ulenMask[$uchr[0] & "\xF0"];
if ($ulen != $j) {
// Put trailing chars in $s
$j -= $ulen;
$i -= $j;
if (0 > $i) {
$s = str_repeat(' ', -$i).$s;
$len -= $i;
$i = 0;
}
while ($j--) {
$s[$i + $j] = $uchr[$ulen + $j];
}
$uchr = substr($uchr, 0, $ulen);
}
}
if (isset($combClass[$uchr])) {
// Combining chars, for sorting
if (!isset($c[$combClass[$uchr]])) {
$c[$combClass[$uchr]] = '';
}
$c[$combClass[$uchr]] .= $uchr;
continue;
}
} else {
// Hangul chars
$uchr = unpack('C*', $uchr);
$j = (($uchr[1] - 224) << 12) + (($uchr[2] - 128) << 6) + $uchr[3] - 0xAC80;
$uchr = "\xE1\x84".\chr(0x80 + (int) ($j / 588))
."\xE1\x85".\chr(0xA1 + (int) (($j % 588) / 28));
if ($j %= 28) {
$uchr .= $j < 25
? ("\xE1\x86".\chr(0xA7 + $j))
: ("\xE1\x87".\chr(0x67 + $j));
}
}
if ($c) {
ksort($c);
$result .= implode('', $c);
$c = array();
}
$result .= $uchr;
}
if ($c) {
ksort($c);
$result .= implode('', $c);
}
return $result;
}
private static function getData($file)
{
if (file_exists($file = __DIR__.'/Resources/unidata/'.$file.'.php')) {
return require $file;
}
return false;
}
}
<?php
class Normalizer extends Symfony\Polyfill\Intl\Normalizer\Normalizer
{
/**
* @deprecated since ICU 56 and removed in PHP 8
*/
const NONE = 1;
const FORM_D = 2;
const FORM_KD = 3;
const FORM_C = 4;
const FORM_KC = 5;
const NFD = 2;
const NFKD = 3;
const NFC = 4;
const NFKC = 5;
}
<?php
return array (
'̀' => 230,
'́' => 230,
'̂' => 230,
'̃' => 230,
'̄' => 230,
'̅' => 230,
'̆' => 230,
'̇' => 230,
'̈' => 230,
'̉' => 230,
'̊' => 230,
'̋' => 230,
'̌' => 230,
'̍' => 230,
'̎' => 230,
'̏' => 230,
'̐' => 230,
'̑' => 230,
'̒' => 230,
'̓' => 230,
'̔' => 230,
'̕' => 232,
'̖' => 220,
'̗' => 220,
'̘' => 220,
'̙' => 220,
'̚' => 232,
'̛' => 216,
'̜' => 220,
'̝' => 220,
'̞' => 220,
'̟' => 220,
'̠' => 220,
'̡' => 202,
'̢' => 202,
'̣' => 220,
'̤' => 220,
'̥' => 220,
'̦' => 220,
'̧' => 202,
'̨' => 202,
'̩' => 220,
'̪' => 220,
'̫' => 220,
'̬' => 220,
'̭' => 220,
'̮' => 220,
'̯' => 220,
'̰' => 220,
'̱' => 220,
'̲' => 220,
'̳' => 220,
'̴' => 1,
'̵' => 1,
'̶' => 1,
'̷' => 1,
'̸' => 1,
'̹' => 220,
'̺' => 220,
'̻' => 220,
'̼' => 220,
'̽' => 230,
'̾' => 230,
'̿' => 230,
'̀' => 230,
'́' => 230,
'͂' => 230,
'̓' => 230,
'̈́' => 230,
'ͅ' => 240,
'͆' => 230,
'͇' => 220,
'͈' => 220,
'͉' => 220,
'͊' => 230,
'͋' => 230,
'͌' => 230,
'͍' => 220,
'͎' => 220,
'͐' => 230,
'͑' => 230,
'͒' => 230,
'͓' => 220,
'͔' => 220,
'͕' => 220,
'͖' => 220,
'͗' => 230,
'͘' => 232,
'͙' => 220,
'͚' => 220,
'͛' => 230,
'͜' => 233,
'͝' => 234,
'͞' => 234,
'͟' => 233,
'͠' => 234,
'͡' => 234,
'͢' => 233,
'ͣ' => 230,
'ͤ' => 230,
'ͥ' => 230,
'ͦ' => 230,
'ͧ' => 230,
'ͨ' => 230,
'ͩ' => 230,
'ͪ' => 230,
'ͫ' => 230,
'ͬ' => 230,
'ͭ' => 230,
'ͮ' => 230,
'ͯ' => 230,
'҃' => 230,
'҄' => 230,
'҅' => 230,
'҆' => 230,
'҇' => 230,
'֑' => 220,
'֒' => 230,
'֓' => 230,
'֔' => 230,
'֕' => 230,
'֖' => 220,
'֗' => 230,
'֘' => 230,
'֙' => 230,
'֚' => 222,
'֛' => 220,
'֜' => 230,
'֝' => 230,
'֞' => 230,
'֟' => 230,
'֠' => 230,
'֡' => 230,
'֢' => 220,
'֣' => 220,
'֤' => 220,
'֥' => 220,
'֦' => 220,
'֧' => 220,
'֨' => 230,
'֩' => 230,
'֪' => 220,
'֫' => 230,
'֬' => 230,
'֭' => 222,
'֮' => 228,
'֯' => 230,
'ְ' => 10,
'ֱ' => 11,
'ֲ' => 12,
'ֳ' => 13,
'ִ' => 14,
'ֵ' => 15,
'ֶ' => 16,
'ַ' => 17,
'ָ' => 18,
'ֹ' => 19,
'ֺ' => 19,
'ֻ' => 20,
'ּ' => 21,
'ֽ' => 22,
'ֿ' => 23,
'ׁ' => 24,
'ׂ' => 25,
'ׄ' => 230,
'ׅ' => 220,
'ׇ' => 18,
'ؐ' => 230,
'ؑ' => 230,
'ؒ' => 230,
'ؓ' => 230,
'ؔ' => 230,
'ؕ' => 230,
'ؖ' => 230,
'ؗ' => 230,
'ؘ' => 30,
'ؙ' => 31,
'ؚ' => 32,
'ً' => 27,
'ٌ' => 28,
'ٍ' => 29,
'َ' => 30,
'ُ' => 31,
'ِ' => 32,
'ّ' => 33,
'ْ' => 34,
'ٓ' => 230,
'ٔ' => 230,
'ٕ' => 220,
'ٖ' => 220,
'ٗ' => 230,
'٘' => 230,
'ٙ' => 230,
'ٚ' => 230,
'ٛ' => 230,
'ٜ' => 220,
'ٝ' => 230,
'ٞ' => 230,
'ٟ' => 220,
'ٰ' => 35,
'ۖ' => 230,
'ۗ' => 230,
'ۘ' => 230,
'ۙ' => 230,
'ۚ' => 230,
'ۛ' => 230,
'ۜ' => 230,
'۟' => 230,
'۠' => 230,
'ۡ' => 230,
'ۢ' => 230,
'ۣ' => 220,
'ۤ' => 230,
'ۧ' => 230,
'ۨ' => 230,
'۪' => 220,
'۫' => 230,
'۬' => 230,
'ۭ' => 220,
'ܑ' => 36,
'ܰ' => 230,
'ܱ' => 220,
'ܲ' => 230,
'ܳ' => 230,
'ܴ' => 220,
'ܵ' => 230,
'ܶ' => 230,
'ܷ' => 220,
'ܸ' => 220,
'ܹ' => 220,
'ܺ' => 230,
'ܻ' => 220,
'ܼ' => 220,
'ܽ' => 230,
'ܾ' => 220,
'ܿ' => 230,
'݀' => 230,
'݁' => 230,
'݂' => 220,
'݃' => 230,
'݄' => 220,
'݅' => 230,
'݆' => 220,
'݇' => 230,
'݈' => 220,
'݉' => 230,
'݊' => 230,
'߫' => 230,
'߬' => 230,
'߭' => 230,
'߮' => 230,
'߯' => 230,
'߰' => 230,
'߱' => 230,
'߲' => 220,
'߳' => 230,
'߽' => 220,
'ࠖ' => 230,
'ࠗ' => 230,
'࠘' => 230,
'࠙' => 230,
'ࠛ' => 230,
'ࠜ' => 230,
'ࠝ' => 230,
'ࠞ' => 230,
'ࠟ' => 230,
'ࠠ' => 230,
'ࠡ' => 230,
'ࠢ' => 230,
'ࠣ' => 230,
'ࠥ' => 230,
'ࠦ' => 230,
'ࠧ' => 230,
'ࠩ' => 230,
'ࠪ' => 230,
'ࠫ' => 230,
'ࠬ' => 230,
'࠭' => 230,
'࡙' => 220,
'࡚' => 220,
'࡛' => 220,
'࣓' => 220,
'ࣔ' => 230,
'ࣕ' => 230,
'ࣖ' => 230,
'ࣗ' => 230,
'ࣘ' => 230,
'ࣙ' => 230,
'ࣚ' => 230,
'ࣛ' => 230,
'ࣜ' => 230,
'ࣝ' => 230,
'ࣞ' => 230,
'ࣟ' => 230,
'࣠' => 230,
'࣡' => 230,
'ࣣ' => 220,
'ࣤ' => 230,
'ࣥ' => 230,
'ࣦ' => 220,
'ࣧ' => 230,
'ࣨ' => 230,
'ࣩ' => 220,
'࣪' => 230,
'࣫' => 230,
'࣬' => 230,
'࣭' => 220,
'࣮' => 220,
'࣯' => 220,
'ࣰ' => 27,
'ࣱ' => 28,
'ࣲ' => 29,
'ࣳ' => 230,
'ࣴ' => 230,
'ࣵ' => 230,
'ࣶ' => 220,
'ࣷ' => 230,
'ࣸ' => 230,
'ࣹ' => 220,
'ࣺ' => 220,
'ࣻ' => 230,
'ࣼ' => 230,
'ࣽ' => 230,
'ࣾ' => 230,
'ࣿ' => 230,
'़' => 7,
'्' => 9,
'॑' => 230,
'॒' => 220,
'॓' => 230,
'॔' => 230,
'়' => 7,
'্' => 9,
'৾' => 230,
'਼' => 7,
'੍' => 9,
'઼' => 7,
'્' => 9,
'଼' => 7,
'୍' => 9,
'்' => 9,
'్' => 9,
'ౕ' => 84,
'ౖ' => 91,
'಼' => 7,
'್' => 9,
'഻' => 9,
'഼' => 9,
'്' => 9,
'්' => 9,
'ุ' => 103,
'ู' => 103,
'ฺ' => 9,
'่' => 107,
'้' => 107,
'๊' => 107,
'๋' => 107,
'ຸ' => 118,
'ູ' => 118,
'຺' => 9,
'່' => 122,
'້' => 122,
'໊' => 122,
'໋' => 122,
'༘' => 220,
'༙' => 220,
'༵' => 220,
'༷' => 220,
'༹' => 216,
'ཱ' => 129,
'ི' => 130,
'ུ' => 132,
'ེ' => 130,
'ཻ' => 130,
'ོ' => 130,
'ཽ' => 130,
'ྀ' => 130,
'ྂ' => 230,
'ྃ' => 230,
'྄' => 9,
'྆' => 230,
'྇' => 230,
'࿆' => 220,
'့' => 7,
'္' => 9,
'်' => 9,
'ႍ' => 220,
'፝' => 230,
'፞' => 230,
'፟' => 230,
'᜔' => 9,
'᜴' => 9,
'្' => 9,
'៝' => 230,
'ᢩ' => 228,
'᤹' => 222,
'᤺' => 230,
'᤻' => 220,
'ᨗ' => 230,
'ᨘ' => 220,
'᩠' => 9,
'᩵' => 230,
'᩶' => 230,
'᩷' => 230,
'᩸' => 230,
'᩹' => 230,
'᩺' => 230,
'᩻' => 230,
'᩼' => 230,
'᩿' => 220,
'᪰' => 230,
'᪱' => 230,
'᪲' => 230,
'᪳' => 230,
'᪴' => 230,
'᪵' => 220,
'᪶' => 220,
'᪷' => 220,
'᪸' => 220,
'᪹' => 220,
'᪺' => 220,
'᪻' => 230,
'᪼' => 230,
'᪽' => 220,
'ᪿ' => 220,
'ᫀ' => 220,
'᬴' => 7,
'᭄' => 9,
'᭫' => 230,
'᭬' => 220,
'᭭' => 230,
'᭮' => 230,
'᭯' => 230,
'᭰' => 230,
'᭱' => 230,
'᭲' => 230,
'᭳' => 230,
'᮪' => 9,
'᮫' => 9,
'᯦' => 7,
'᯲' => 9,
'᯳' => 9,
'᰷' => 7,
'᳐' => 230,
'᳑' => 230,
'᳒' => 230,
'᳔' => 1,
'᳕' => 220,
'᳖' => 220,
'᳗' => 220,
'᳘' => 220,
'᳙' => 220,
'᳚' => 230,
'᳛' => 230,
'᳜' => 220,
'᳝' => 220,
'᳞' => 220,
'᳟' => 220,
'᳠' => 230,
'᳢' => 1,
'᳣' => 1,
'᳤' => 1,
'᳥' => 1,
'᳦' => 1,
'᳧' => 1,
'᳨' => 1,
'᳭' => 220,
'᳴' => 230,
'᳸' => 230,
'᳹' => 230,
'᷀' => 230,
'᷁' => 230,
'᷂' => 220,
'᷃' => 230,
'᷄' => 230,
'᷅' => 230,
'᷆' => 230,
'᷇' => 230,
'᷈' => 230,
'᷉' => 230,
'᷊' => 220,
'᷋' => 230,
'᷌' => 230,
'᷍' => 234,
'᷎' => 214,
'᷏' => 220,
'᷐' => 202,
'᷑' => 230,
'᷒' => 230,
'ᷓ' => 230,
'ᷔ' => 230,
'ᷕ' => 230,
'ᷖ' => 230,
'ᷗ' => 230,
'ᷘ' => 230,
'ᷙ' => 230,
'ᷚ' => 230,
'ᷛ' => 230,
'ᷜ' => 230,
'ᷝ' => 230,
'ᷞ' => 230,
'ᷟ' => 230,
'ᷠ' => 230,
'ᷡ' => 230,
'ᷢ' => 230,
'ᷣ' => 230,
'ᷤ' => 230,
'ᷥ' => 230,
'ᷦ' => 230,
'ᷧ' => 230,
'ᷨ' => 230,
'ᷩ' => 230,
'ᷪ' => 230,
'ᷫ' => 230,
'ᷬ' => 230,
'ᷭ' => 230,
'ᷮ' => 230,
'ᷯ' => 230,
'ᷰ' => 230,
'ᷱ' => 230,
'ᷲ' => 230,
'ᷳ' => 230,
'ᷴ' => 230,
'᷵' => 230,
'᷶' => 232,
'᷷' => 228,
'᷸' => 228,
'᷹' => 220,
'᷻' => 230,
'᷼' => 233,
'᷽' => 220,
'᷾' => 230,
'᷿' => 220,
'⃐' => 230,
'⃑' => 230,
'⃒' => 1,
'⃓' => 1,
'⃔' => 230,
'⃕' => 230,
'⃖' => 230,
'⃗' => 230,
'⃘' => 1,
'⃙' => 1,
'⃚' => 1,
'⃛' => 230,
'⃜' => 230,
'⃡' => 230,
'⃥' => 1,
'⃦' => 1,
'⃧' => 230,
'⃨' => 220,
'⃩' => 230,
'⃪' => 1,
'⃫' => 1,
'⃬' => 220,
'⃭' => 220,
'⃮' => 220,
'⃯' => 220,
'⃰' => 230,
'⳯' => 230,
'⳰' => 230,
'⳱' => 230,
'⵿' => 9,
'ⷠ' => 230,
'ⷡ' => 230,
'ⷢ' => 230,
'ⷣ' => 230,
'ⷤ' => 230,
'ⷥ' => 230,
'ⷦ' => 230,
'ⷧ' => 230,
'ⷨ' => 230,
'ⷩ' => 230,
'ⷪ' => 230,
'ⷫ' => 230,
'ⷬ' => 230,
'ⷭ' => 230,
'ⷮ' => 230,
'ⷯ' => 230,
'ⷰ' => 230,
'ⷱ' => 230,
'ⷲ' => 230,
'ⷳ' => 230,
'ⷴ' => 230,
'ⷵ' => 230,
'ⷶ' => 230,
'ⷷ' => 230,
'ⷸ' => 230,
'ⷹ' => 230,
'ⷺ' => 230,
'ⷻ' => 230,
'ⷼ' => 230,
'ⷽ' => 230,
'ⷾ' => 230,
'ⷿ' => 230,
'〪' => 218,
'〫' => 228,
'〬' => 232,
'〭' => 222,
'〮' => 224,
'〯' => 224,
'゙' => 8,
'゚' => 8,
'꙯' => 230,
'ꙴ' => 230,
'ꙵ' => 230,
'ꙶ' => 230,
'ꙷ' => 230,
'ꙸ' => 230,
'ꙹ' => 230,
'ꙺ' => 230,
'ꙻ' => 230,
'꙼' => 230,
'꙽' => 230,
'ꚞ' => 230,
'ꚟ' => 230,
'꛰' => 230,
'꛱' => 230,
'꠆' => 9,
'꠬' => 9,
'꣄' => 9,
'꣠' => 230,
'꣡' => 230,
'꣢' => 230,
'꣣' => 230,
'꣤' => 230,
'꣥' => 230,
'꣦' => 230,
'꣧' => 230,
'꣨' => 230,
'꣩' => 230,
'꣪' => 230,
'꣫' => 230,
'꣬' => 230,
'꣭' => 230,
'꣮' => 230,
'꣯' => 230,
'꣰' => 230,
'꣱' => 230,
'꤫' => 220,
'꤬' => 220,
'꤭' => 220,
'꥓' => 9,
'꦳' => 7,
'꧀' => 9,
'ꪰ' => 230,
'ꪲ' => 230,
'ꪳ' => 230,
'ꪴ' => 220,
'ꪷ' => 230,
'ꪸ' => 230,
'ꪾ' => 230,
'꪿' => 230,
'꫁' => 230,
'꫶' => 9,
'꯭' => 9,
'ﬞ' => 26,
'︠' => 230,
'︡' => 230,
'︢' => 230,
'︣' => 230,
'︤' => 230,
'︥' => 230,
'︦' => 230,
'︧' => 220,
'︨' => 220,
'︩' => 220,
'︪' => 220,
'︫' => 220,
'︬' => 220,
'︭' => 220,
'︮' => 230,
'︯' => 230,
'𐇽' => 220,
'𐋠' => 220,
'𐍶' => 230,
'𐍷' => 230,
'𐍸' => 230,
'𐍹' => 230,
'𐍺' => 230,
'𐨍' => 220,
'𐨏' => 230,
'𐨸' => 230,
'𐨹' => 1,
'𐨺' => 220,
'𐨿' => 9,
'𐫥' => 230,
'𐫦' => 220,
'𐴤' => 230,
'𐴥' => 230,
'𐴦' => 230,
'𐴧' => 230,
'𐺫' => 230,
'𐺬' => 230,
'𐽆' => 220,
'𐽇' => 220,
'𐽈' => 230,
'𐽉' => 230,
'𐽊' => 230,
'𐽋' => 220,
'𐽌' => 230,
'𐽍' => 220,
'𐽎' => 220,
'𐽏' => 220,
'𐽐' => 220,
'𑁆' => 9,
'𑁿' => 9,
'𑂹' => 9,
'𑂺' => 7,
'𑄀' => 230,
'𑄁' => 230,
'𑄂' => 230,
'𑄳' => 9,
'𑄴' => 9,
'𑅳' => 7,
'𑇀' => 9,
'𑇊' => 7,
'𑈵' => 9,
'𑈶' => 7,
'𑋩' => 7,
'𑋪' => 9,
'𑌻' => 7,
'𑌼' => 7,
'𑍍' => 9,
'𑍦' => 230,
'𑍧' => 230,
'𑍨' => 230,
'𑍩' => 230,
'𑍪' => 230,
'𑍫' => 230,
'𑍬' => 230,
'𑍰' => 230,
'𑍱' => 230,
'𑍲' => 230,
'𑍳' => 230,
'𑍴' => 230,
'𑑂' => 9,
'𑑆' => 7,
'𑑞' => 230,
'𑓂' => 9,
'𑓃' => 7,
'𑖿' => 9,
'𑗀' => 7,
'𑘿' => 9,
'𑚶' => 9,
'𑚷' => 7,
'𑜫' => 9,
'𑠹' => 9,
'𑠺' => 7,
'𑤽' => 9,
'𑤾' => 9,
'𑥃' => 7,
'𑧠' => 9,
'𑨴' => 9,
'𑩇' => 9,
'𑪙' => 9,
'𑰿' => 9,
'𑵂' => 7,
'𑵄' => 9,
'𑵅' => 9,
'𑶗' => 9,
'𖫰' => 1,
'𖫱' => 1,
'𖫲' => 1,
'𖫳' => 1,
'𖫴' => 1,
'𖬰' => 230,
'𖬱' => 230,
'𖬲' => 230,
'𖬳' => 230,
'𖬴' => 230,
'𖬵' => 230,
'𖬶' => 230,
'𖿰' => 6,
'𖿱' => 6,
'𛲞' => 1,
'𝅥' => 216,
'𝅦' => 216,
'𝅧' => 1,
'𝅨' => 1,
'𝅩' => 1,
'𝅭' => 226,
'𝅮' => 216,
'𝅯' => 216,
'𝅰' => 216,
'𝅱' => 216,
'𝅲' => 216,
'𝅻' => 220,
'𝅼' => 220,
'𝅽' => 220,
'𝅾' => 220,
'𝅿' => 220,
'𝆀' => 220,
'𝆁' => 220,
'𝆂' => 220,
'𝆅' => 230,
'𝆆' => 230,
'𝆇' => 230,
'𝆈' => 230,
'𝆉' => 230,
'𝆊' => 220,
'𝆋' => 220,
'𝆪' => 230,
'𝆫' => 230,
'𝆬' => 230,
'𝆭' => 230,
'𝉂' => 230,
'𝉃' => 230,
'𝉄' => 230,
'𞀀' => 230,
'𞀁' => 230,
'𞀂' => 230,
'𞀃' => 230,
'𞀄' => 230,
'𞀅' => 230,
'𞀆' => 230,
'𞀈' => 230,
'𞀉' => 230,
'𞀊' => 230,
'𞀋' => 230,
'𞀌' => 230,
'𞀍' => 230,
'𞀎' => 230,
'𞀏' => 230,
'𞀐' => 230,
'𞀑' => 230,
'𞀒' => 230,
'𞀓' => 230,
'𞀔' => 230,
'𞀕' => 230,
'𞀖' => 230,
'𞀗' => 230,
'𞀘' => 230,
'𞀛' => 230,
'𞀜' => 230,
'𞀝' => 230,
'𞀞' => 230,
'𞀟' => 230,
'𞀠' => 230,
'𞀡' => 230,
'𞀣' => 230,
'𞀤' => 230,
'𞀦' => 230,
'𞀧' => 230,
'𞀨' => 230,
'𞀩' => 230,
'𞀪' => 230,
'𞄰' => 230,
'𞄱' => 230,
'𞄲' => 230,
'𞄳' => 230,
'𞄴' => 230,
'𞄵' => 230,
'𞄶' => 230,
'𞋬' => 230,
'𞋭' => 230,
'𞋮' => 230,
'𞋯' => 230,
'𞣐' => 220,
'𞣑' => 220,
'𞣒' => 220,
'𞣓' => 220,
'𞣔' => 220,
'𞣕' => 220,
'𞣖' => 220,
'𞥄' => 230,
'𞥅' => 230,
'𞥆' => 230,
'𞥇' => 230,
'𞥈' => 230,
'𞥉' => 230,
'𞥊' => 7,
);
<?php
return array (
'À' => 'À',
'Á' => 'Á',
'Â' => 'Â',
'Ã' => 'Ã',
'Ä' => 'Ä',
'Å' => 'Å',
'Ç' => 'Ç',
'È' => 'È',
'É' => 'É',
'Ê' => 'Ê',
'Ë' => 'Ë',
'Ì' => 'Ì',
'Í' => 'Í',
'Î' => 'Î',
'Ï' => 'Ï',
'Ñ' => 'Ñ',
'Ò' => 'Ò',
'Ó' => 'Ó',
'Ô' => 'Ô',
'Õ' => 'Õ',
'Ö' => 'Ö',
'Ù' => 'Ù',
'Ú' => 'Ú',
'Û' => 'Û',
'Ü' => 'Ü',
'Ý' => 'Ý',
'à' => 'à',
'á' => 'á',
'â' => 'â',
'ã' => 'ã',
'ä' => 'ä',
'å' => 'å',
'ç' => 'ç',
'è' => 'è',
'é' => 'é',
'ê' => 'ê',
'ë' => 'ë',
'ì' => 'ì',
'í' => 'í',
'î' => 'î',
'ï' => 'ï',
'ñ' => 'ñ',
'ò' => 'ò',
'ó' => 'ó',
'ô' => 'ô',
'õ' => 'õ',
'ö' => 'ö',
'ù' => 'ù',
'ú' => 'ú',
'û' => 'û',
'ü' => 'ü',
'ý' => 'ý',
'ÿ' => 'ÿ',
'Ā' => 'Ā',
'ā' => 'ā',
'Ă' => 'Ă',
'ă' => 'ă',
'Ą' => 'Ą',
'ą' => 'ą',
'Ć' => 'Ć',
'ć' => 'ć',
'Ĉ' => 'Ĉ',
'ĉ' => 'ĉ',
'Ċ' => 'Ċ',
'ċ' => 'ċ',
'Č' => 'Č',
'č' => 'č',
'Ď' => 'Ď',
'ď' => 'ď',
'Ē' => 'Ē',
'ē' => 'ē',
'Ĕ' => 'Ĕ',
'ĕ' => 'ĕ',
'Ė' => 'Ė',
'ė' => 'ė',
'Ę' => 'Ę',
'ę' => 'ę',
'Ě' => 'Ě',
'ě' => 'ě',
'Ĝ' => 'Ĝ',
'ĝ' => 'ĝ',
'Ğ' => 'Ğ',
'ğ' => 'ğ',
'Ġ' => 'Ġ',
'ġ' => 'ġ',
'Ģ' => 'Ģ',
'ģ' => 'ģ',
'Ĥ' => 'Ĥ',
'ĥ' => 'ĥ',
'Ĩ' => 'Ĩ',
'ĩ' => 'ĩ',
'Ī' => 'Ī',
'ī' => 'ī',
'Ĭ' => 'Ĭ',
'ĭ' => 'ĭ',
'Į' => 'Į',
'į' => 'į',
'İ' => 'İ',
'Ĵ' => 'Ĵ',
'ĵ' => 'ĵ',
'Ķ' => 'Ķ',
'ķ' => 'ķ',
'Ĺ' => 'Ĺ',
'ĺ' => 'ĺ',
'Ļ' => 'Ļ',
'ļ' => 'ļ',
'Ľ' => 'Ľ',
'ľ' => 'ľ',
'Ń' => 'Ń',
'ń' => 'ń',
'Ņ' => 'Ņ',
'ņ' => 'ņ',
'Ň' => 'Ň',
'ň' => 'ň',
'Ō' => 'Ō',
'ō' => 'ō',
'Ŏ' => 'Ŏ',
'ŏ' => 'ŏ',
'Ő' => 'Ő',
'ő' => 'ő',
'Ŕ' => 'Ŕ',
'ŕ' => 'ŕ',
'Ŗ' => 'Ŗ',
'ŗ' => 'ŗ',
'Ř' => 'Ř',
'ř' => 'ř',
'Ś' => 'Ś',
'ś' => 'ś',
'Ŝ' => 'Ŝ',
'ŝ' => 'ŝ',
'Ş' => 'Ş',
'ş' => 'ş',
'Š' => 'Š',
'š' => 'š',
'Ţ' => 'Ţ',
'ţ' => 'ţ',
'Ť' => 'Ť',
'ť' => 'ť',
'Ũ' => 'Ũ',
'ũ' => 'ũ',
'Ū' => 'Ū',
'ū' => 'ū',
'Ŭ' => 'Ŭ',
'ŭ' => 'ŭ',
'Ů' => 'Ů',
'ů' => 'ů',
'Ű' => 'Ű',
'ű' => 'ű',
'Ų' => 'Ų',
'ų' => 'ų',
'Ŵ' => 'Ŵ',
'ŵ' => 'ŵ',
'Ŷ' => 'Ŷ',
'ŷ' => 'ŷ',
'Ÿ' => 'Ÿ',
'Ź' => 'Ź',
'ź' => 'ź',
'Ż' => 'Ż',
'ż' => 'ż',
'Ž' => 'Ž',
'ž' => 'ž',
'Ơ' => 'Ơ',
'ơ' => 'ơ',
'Ư' => 'Ư',
'ư' => 'ư',
'Ǎ' => 'Ǎ',
'ǎ' => 'ǎ',
'Ǐ' => 'Ǐ',
'ǐ' => 'ǐ',
'Ǒ' => 'Ǒ',
'ǒ' => 'ǒ',
'Ǔ' => 'Ǔ',
'ǔ' => 'ǔ',
'Ǖ' => 'Ǖ',
'ǖ' => 'ǖ',
'Ǘ' => 'Ǘ',
'ǘ' => 'ǘ',
'Ǚ' => 'Ǚ',
'ǚ' => 'ǚ',
'Ǜ' => 'Ǜ',
'ǜ' => 'ǜ',
'Ǟ' => 'Ǟ',
'ǟ' => 'ǟ',
'Ǡ' => 'Ǡ',
'ǡ' => 'ǡ',
'Ǣ' => 'Ǣ',
'ǣ' => 'ǣ',
'Ǧ' => 'Ǧ',
'ǧ' => 'ǧ',
'Ǩ' => 'Ǩ',
'ǩ' => 'ǩ',
'Ǫ' => 'Ǫ',
'ǫ' => 'ǫ',
'Ǭ' => 'Ǭ',
'ǭ' => 'ǭ',
'Ǯ' => 'Ǯ',
'ǯ' => 'ǯ',
'ǰ' => 'ǰ',
'Ǵ' => 'Ǵ',
'ǵ' => 'ǵ',
'Ǹ' => 'Ǹ',
'ǹ' => 'ǹ',
'Ǻ' => 'Ǻ',
'ǻ' => 'ǻ',
'Ǽ' => 'Ǽ',
'ǽ' => 'ǽ',
'Ǿ' => 'Ǿ',
'ǿ' => 'ǿ',
'Ȁ' => 'Ȁ',
'ȁ' => 'ȁ',
'Ȃ' => 'Ȃ',
'ȃ' => 'ȃ',
'Ȅ' => 'Ȅ',
'ȅ' => 'ȅ',
'Ȇ' => 'Ȇ',
'ȇ' => 'ȇ',
'Ȉ' => 'Ȉ',
'ȉ' => 'ȉ',
'Ȋ' => 'Ȋ',
'ȋ' => 'ȋ',
'Ȍ' => 'Ȍ',
'ȍ' => 'ȍ',
'Ȏ' => 'Ȏ',
'ȏ' => 'ȏ',
'Ȑ' => 'Ȑ',
'ȑ' => 'ȑ',
'Ȓ' => 'Ȓ',
'ȓ' => 'ȓ',
'Ȕ' => 'Ȕ',
'ȕ' => 'ȕ',
'Ȗ' => 'Ȗ',
'ȗ' => 'ȗ',
'Ș' => 'Ș',
'ș' => 'ș',
'Ț' => 'Ț',
'ț' => 'ț',
'Ȟ' => 'Ȟ',
'ȟ' => 'ȟ',
'Ȧ' => 'Ȧ',
'ȧ' => 'ȧ',
'Ȩ' => 'Ȩ',
'ȩ' => 'ȩ',
'Ȫ' => 'Ȫ',
'ȫ' => 'ȫ',
'Ȭ' => 'Ȭ',
'ȭ' => 'ȭ',
'Ȯ' => 'Ȯ',
'ȯ' => 'ȯ',
'Ȱ' => 'Ȱ',
'ȱ' => 'ȱ',
'Ȳ' => 'Ȳ',
'ȳ' => 'ȳ',
'΅' => '΅',
'Ά' => 'Ά',
'Έ' => 'Έ',
'Ή' => 'Ή',
'Ί' => 'Ί',
'Ό' => 'Ό',
'Ύ' => 'Ύ',
'Ώ' => 'Ώ',
'ΐ' => 'ΐ',
'Ϊ' => 'Ϊ',
'Ϋ' => 'Ϋ',
'ά' => 'ά',
'έ' => 'έ',
'ή' => 'ή',
'ί' => 'ί',
'ΰ' => 'ΰ',
'ϊ' => 'ϊ',
'ϋ' => 'ϋ',
'ό' => 'ό',
'ύ' => 'ύ',
'ώ' => 'ώ',
'ϓ' => 'ϓ',
'ϔ' => 'ϔ',
'Ѐ' => 'Ѐ',
'Ё' => 'Ё',
'Ѓ' => 'Ѓ',
'Ї' => 'Ї',
'Ќ' => 'Ќ',
'Ѝ' => 'Ѝ',
'Ў' => 'Ў',
'Й' => 'Й',
'й' => 'й',
'ѐ' => 'ѐ',
'ё' => 'ё',
'ѓ' => 'ѓ',
'ї' => 'ї',
'ќ' => 'ќ',
'ѝ' => 'ѝ',
'ў' => 'ў',
'Ѷ' => 'Ѷ',
'ѷ' => 'ѷ',
'Ӂ' => 'Ӂ',
'ӂ' => 'ӂ',
'Ӑ' => 'Ӑ',
'ӑ' => 'ӑ',
'Ӓ' => 'Ӓ',
'ӓ' => 'ӓ',
'Ӗ' => 'Ӗ',
'ӗ' => 'ӗ',
'Ӛ' => 'Ӛ',
'ӛ' => 'ӛ',
'Ӝ' => 'Ӝ',
'ӝ' => 'ӝ',
'Ӟ' => 'Ӟ',
'ӟ' => 'ӟ',
'Ӣ' => 'Ӣ',
'ӣ' => 'ӣ',
'Ӥ' => 'Ӥ',
'ӥ' => 'ӥ',
'Ӧ' => 'Ӧ',
'ӧ' => 'ӧ',
'Ӫ' => 'Ӫ',
'ӫ' => 'ӫ',
'Ӭ' => 'Ӭ',
'ӭ' => 'ӭ',
'Ӯ' => 'Ӯ',
'ӯ' => 'ӯ',
'Ӱ' => 'Ӱ',
'ӱ' => 'ӱ',
'Ӳ' => 'Ӳ',
'ӳ' => 'ӳ',
'Ӵ' => 'Ӵ',
'ӵ' => 'ӵ',
'Ӹ' => 'Ӹ',
'ӹ' => 'ӹ',
'آ' => 'آ',
'أ' => 'أ',
'ؤ' => 'ؤ',
'إ' => 'إ',
'ئ' => 'ئ',
'ۀ' => 'ۀ',
'ۂ' => 'ۂ',
'ۓ' => 'ۓ',
'ऩ' => 'ऩ',
'ऱ' => 'ऱ',
'ऴ' => 'ऴ',
'ো' => 'ো',
'ৌ' => 'ৌ',
'ୈ' => 'ୈ',
'ୋ' => 'ୋ',
'ୌ' => 'ୌ',
'ஔ' => 'ஔ',
'ொ' => 'ொ',
'ோ' => 'ோ',
'ௌ' => 'ௌ',
'ై' => 'ై',
'ೀ' => 'ೀ',
'ೇ' => 'ೇ',
'ೈ' => 'ೈ',
'ೊ' => 'ೊ',
'ೋ' => 'ೋ',
'ൊ' => 'ൊ',
'ോ' => 'ോ',
'ൌ' => 'ൌ',
'ේ' => 'ේ',
'ො' => 'ො',
'ෝ' => 'ෝ',
'ෞ' => 'ෞ',
'ဦ' => 'ဦ',
'ᬆ' => 'ᬆ',
'ᬈ' => 'ᬈ',
'ᬊ' => 'ᬊ',
'ᬌ' => 'ᬌ',
'ᬎ' => 'ᬎ',
'ᬒ' => 'ᬒ',
'ᬻ' => 'ᬻ',
'ᬽ' => 'ᬽ',
'ᭀ' => 'ᭀ',
'ᭁ' => 'ᭁ',
'ᭃ' => 'ᭃ',
'Ḁ' => 'Ḁ',
'ḁ' => 'ḁ',
'Ḃ' => 'Ḃ',
'ḃ' => 'ḃ',
'Ḅ' => 'Ḅ',
'ḅ' => 'ḅ',
'Ḇ' => 'Ḇ',
'ḇ' => 'ḇ',
'Ḉ' => 'Ḉ',
'ḉ' => 'ḉ',
'Ḋ' => 'Ḋ',
'ḋ' => 'ḋ',
'Ḍ' => 'Ḍ',
'ḍ' => 'ḍ',
'Ḏ' => 'Ḏ',
'ḏ' => 'ḏ',
'Ḑ' => 'Ḑ',
'ḑ' => 'ḑ',
'Ḓ' => 'Ḓ',
'ḓ' => 'ḓ',
'Ḕ' => 'Ḕ',
'ḕ' => 'ḕ',
'Ḗ' => 'Ḗ',
'ḗ' => 'ḗ',
'Ḙ' => 'Ḙ',
'ḙ' => 'ḙ',
'Ḛ' => 'Ḛ',
'ḛ' => 'ḛ',
'Ḝ' => 'Ḝ',
'ḝ' => 'ḝ',
'Ḟ' => 'Ḟ',
'ḟ' => 'ḟ',
'Ḡ' => 'Ḡ',
'ḡ' => 'ḡ',
'Ḣ' => 'Ḣ',
'ḣ' => 'ḣ',
'Ḥ' => 'Ḥ',
'ḥ' => 'ḥ',
'Ḧ' => 'Ḧ',
'ḧ' => 'ḧ',
'Ḩ' => 'Ḩ',
'ḩ' => 'ḩ',
'Ḫ' => 'Ḫ',
'ḫ' => 'ḫ',
'Ḭ' => 'Ḭ',
'ḭ' => 'ḭ',
'Ḯ' => 'Ḯ',
'ḯ' => 'ḯ',
'Ḱ' => 'Ḱ',
'ḱ' => 'ḱ',
'Ḳ' => 'Ḳ',
'ḳ' => 'ḳ',
'Ḵ' => 'Ḵ',
'ḵ' => 'ḵ',
'Ḷ' => 'Ḷ',
'ḷ' => 'ḷ',
'Ḹ' => 'Ḹ',
'ḹ' => 'ḹ',
'Ḻ' => 'Ḻ',
'ḻ' => 'ḻ',
'Ḽ' => 'Ḽ',
'ḽ' => 'ḽ',
'Ḿ' => 'Ḿ',
'ḿ' => 'ḿ',
'Ṁ' => 'Ṁ',
'ṁ' => 'ṁ',
'Ṃ' => 'Ṃ',
'ṃ' => 'ṃ',
'Ṅ' => 'Ṅ',
'ṅ' => 'ṅ',
'Ṇ' => 'Ṇ',
'ṇ' => 'ṇ',
'Ṉ' => 'Ṉ',
'ṉ' => 'ṉ',
'Ṋ' => 'Ṋ',
'ṋ' => 'ṋ',
'Ṍ' => 'Ṍ',
'ṍ' => 'ṍ',
'Ṏ' => 'Ṏ',
'ṏ' => 'ṏ',
'Ṑ' => 'Ṑ',
'ṑ' => 'ṑ',
'Ṓ' => 'Ṓ',
'ṓ' => 'ṓ',
'Ṕ' => 'Ṕ',
'ṕ' => 'ṕ',
'Ṗ' => 'Ṗ',
'ṗ' => 'ṗ',
'Ṙ' => 'Ṙ',
'ṙ' => 'ṙ',
'Ṛ' => 'Ṛ',
'ṛ' => 'ṛ',
'Ṝ' => 'Ṝ',
'ṝ' => 'ṝ',
'Ṟ' => 'Ṟ',
'ṟ' => 'ṟ',
'Ṡ' => 'Ṡ',
'ṡ' => 'ṡ',
'Ṣ' => 'Ṣ',
'ṣ' => 'ṣ',
'Ṥ' => 'Ṥ',
'ṥ' => 'ṥ',
'Ṧ' => 'Ṧ',
'ṧ' => 'ṧ',
'Ṩ' => 'Ṩ',
'ṩ' => 'ṩ',
'Ṫ' => 'Ṫ',
'ṫ' => 'ṫ',
'Ṭ' => 'Ṭ',
'ṭ' => 'ṭ',
'Ṯ' => 'Ṯ',
'ṯ' => 'ṯ',
'Ṱ' => 'Ṱ',
'ṱ' => 'ṱ',
'Ṳ' => 'Ṳ',
'ṳ' => 'ṳ',
'Ṵ' => 'Ṵ',
'ṵ' => 'ṵ',
'Ṷ' => 'Ṷ',
'ṷ' => 'ṷ',
'Ṹ' => 'Ṹ',
'ṹ' => 'ṹ',
'Ṻ' => 'Ṻ',
'ṻ' => 'ṻ',
'Ṽ' => 'Ṽ',
'ṽ' => 'ṽ',
'Ṿ' => 'Ṿ',
'ṿ' => 'ṿ',
'Ẁ' => 'Ẁ',
'ẁ' => 'ẁ',
'Ẃ' => 'Ẃ',
'ẃ' => 'ẃ',
'Ẅ' => 'Ẅ',
'ẅ' => 'ẅ',
'Ẇ' => 'Ẇ',
'ẇ' => 'ẇ',
'Ẉ' => 'Ẉ',
'ẉ' => 'ẉ',
'Ẋ' => 'Ẋ',
'ẋ' => 'ẋ',
'Ẍ' => 'Ẍ',
'ẍ' => 'ẍ',
'Ẏ' => 'Ẏ',
'ẏ' => 'ẏ',
'Ẑ' => 'Ẑ',
'ẑ' => 'ẑ',
'Ẓ' => 'Ẓ',
'ẓ' => 'ẓ',
'Ẕ' => 'Ẕ',
'ẕ' => 'ẕ',
'ẖ' => 'ẖ',
'ẗ' => 'ẗ',
'ẘ' => 'ẘ',
'ẙ' => 'ẙ',
'ẛ' => 'ẛ',
'Ạ' => 'Ạ',
'ạ' => 'ạ',
'Ả' => 'Ả',
'ả' => 'ả',
'Ấ' => 'Ấ',
'ấ' => 'ấ',
'Ầ' => 'Ầ',
'ầ' => 'ầ',
'Ẩ' => 'Ẩ',
'ẩ' => 'ẩ',
'Ẫ' => 'Ẫ',
'ẫ' => 'ẫ',
'Ậ' => 'Ậ',
'ậ' => 'ậ',
'Ắ' => 'Ắ',
'ắ' => 'ắ',
'Ằ' => 'Ằ',
'ằ' => 'ằ',
'Ẳ' => 'Ẳ',
'ẳ' => 'ẳ',
'Ẵ' => 'Ẵ',
'ẵ' => 'ẵ',
'Ặ' => 'Ặ',
'ặ' => 'ặ',
'Ẹ' => 'Ẹ',
'ẹ' => 'ẹ',
'Ẻ' => 'Ẻ',
'ẻ' => 'ẻ',
'Ẽ' => 'Ẽ',
'ẽ' => 'ẽ',
'Ế' => 'Ế',
'ế' => 'ế',
'Ề' => 'Ề',
'ề' => 'ề',
'Ể' => 'Ể',
'ể' => 'ể',
'Ễ' => 'Ễ',
'ễ' => 'ễ',
'Ệ' => 'Ệ',
'ệ' => 'ệ',
'Ỉ' => 'Ỉ',
'ỉ' => 'ỉ',
'Ị' => 'Ị',
'ị' => 'ị',
'Ọ' => 'Ọ',
'ọ' => 'ọ',
'Ỏ' => 'Ỏ',
'ỏ' => 'ỏ',
'Ố' => 'Ố',
'ố' => 'ố',
'Ồ' => 'Ồ',
'ồ' => 'ồ',
'Ổ' => 'Ổ',
'ổ' => 'ổ',
'Ỗ' => 'Ỗ',
'ỗ' => 'ỗ',
'Ộ' => 'Ộ',
'ộ' => 'ộ',
'Ớ' => 'Ớ',
'ớ' => 'ớ',
'Ờ' => 'Ờ',
'ờ' => 'ờ',
'Ở' => 'Ở',
'ở' => 'ở',
'Ỡ' => 'Ỡ',
'ỡ' => 'ỡ',
'Ợ' => 'Ợ',
'ợ' => 'ợ',
'Ụ' => 'Ụ',
'ụ' => 'ụ',
'Ủ' => 'Ủ',
'ủ' => 'ủ',
'Ứ' => 'Ứ',
'ứ' => 'ứ',
'Ừ' => 'Ừ',
'ừ' => 'ừ',
'Ử' => 'Ử',
'ử' => 'ử',
'Ữ' => 'Ữ',
'ữ' => 'ữ',
'Ự' => 'Ự',
'ự' => 'ự',
'Ỳ' => 'Ỳ',
'ỳ' => 'ỳ',
'Ỵ' => 'Ỵ',
'ỵ' => 'ỵ',
'Ỷ' => 'Ỷ',
'ỷ' => 'ỷ',
'Ỹ' => 'Ỹ',
'ỹ' => 'ỹ',
'ἀ' => 'ἀ',
'ἁ' => 'ἁ',
'ἂ' => 'ἂ',
'ἃ' => 'ἃ',
'ἄ' => 'ἄ',
'ἅ' => 'ἅ',
'ἆ' => 'ἆ',
'ἇ' => 'ἇ',
'Ἀ' => 'Ἀ',
'Ἁ' => 'Ἁ',
'Ἂ' => 'Ἂ',
'Ἃ' => 'Ἃ',
'Ἄ' => 'Ἄ',
'Ἅ' => 'Ἅ',
'Ἆ' => 'Ἆ',
'Ἇ' => 'Ἇ',
'ἐ' => 'ἐ',
'ἑ' => 'ἑ',
'ἒ' => 'ἒ',
'ἓ' => 'ἓ',
'ἔ' => 'ἔ',
'ἕ' => 'ἕ',
'Ἐ' => 'Ἐ',
'Ἑ' => 'Ἑ',
'Ἒ' => 'Ἒ',
'Ἓ' => 'Ἓ',
'Ἔ' => 'Ἔ',
'Ἕ' => 'Ἕ',
'ἠ' => 'ἠ',
'ἡ' => 'ἡ',
'ἢ' => 'ἢ',
'ἣ' => 'ἣ',
'ἤ' => 'ἤ',
'ἥ' => 'ἥ',
'ἦ' => 'ἦ',
'ἧ' => 'ἧ',
'Ἠ' => 'Ἠ',
'Ἡ' => 'Ἡ',
'Ἢ' => 'Ἢ',
'Ἣ' => 'Ἣ',
'Ἤ' => 'Ἤ',
'Ἥ' => 'Ἥ',
'Ἦ' => 'Ἦ',
'Ἧ' => 'Ἧ',
'ἰ' => 'ἰ',
'ἱ' => 'ἱ',
'ἲ' => 'ἲ',
'ἳ' => 'ἳ',
'ἴ' => 'ἴ',
'ἵ' => 'ἵ',
'ἶ' => 'ἶ',
'ἷ' => 'ἷ',
'Ἰ' => 'Ἰ',
'Ἱ' => 'Ἱ',
'Ἲ' => 'Ἲ',
'Ἳ' => 'Ἳ',
'Ἴ' => 'Ἴ',
'Ἵ' => 'Ἵ',
'Ἶ' => 'Ἶ',
'Ἷ' => 'Ἷ',
'ὀ' => 'ὀ',
'ὁ' => 'ὁ',
'ὂ' => 'ὂ',
'ὃ' => 'ὃ',
'ὄ' => 'ὄ',
'ὅ' => 'ὅ',
'Ὀ' => 'Ὀ',
'Ὁ' => 'Ὁ',
'Ὂ' => 'Ὂ',
'Ὃ' => 'Ὃ',
'Ὄ' => 'Ὄ',
'Ὅ' => 'Ὅ',
'ὐ' => 'ὐ',
'ὑ' => 'ὑ',
'ὒ' => 'ὒ',
'ὓ' => 'ὓ',
'ὔ' => 'ὔ',
'ὕ' => 'ὕ',
'ὖ' => 'ὖ',
'ὗ' => 'ὗ',
'Ὑ' => 'Ὑ',
'Ὓ' => 'Ὓ',
'Ὕ' => 'Ὕ',
'Ὗ' => 'Ὗ',
'ὠ' => 'ὠ',
'ὡ' => 'ὡ',
'ὢ' => 'ὢ',
'ὣ' => 'ὣ',
'ὤ' => 'ὤ',
'ὥ' => 'ὥ',
'ὦ' => 'ὦ',
'ὧ' => 'ὧ',
'Ὠ' => 'Ὠ',
'Ὡ' => 'Ὡ',
'Ὢ' => 'Ὢ',
'Ὣ' => 'Ὣ',
'Ὤ' => 'Ὤ',
'Ὥ' => 'Ὥ',
'Ὦ' => 'Ὦ',
'Ὧ' => 'Ὧ',
'ὰ' => 'ὰ',
'ὲ' => 'ὲ',
'ὴ' => 'ὴ',
'ὶ' => 'ὶ',
'ὸ' => 'ὸ',
'ὺ' => 'ὺ',
'ὼ' => 'ὼ',
'ᾀ' => 'ᾀ',
'ᾁ' => 'ᾁ',
'ᾂ' => 'ᾂ',
'ᾃ' => 'ᾃ',
'ᾄ' => 'ᾄ',
'ᾅ' => 'ᾅ',
'ᾆ' => 'ᾆ',
'ᾇ' => 'ᾇ',
'ᾈ' => 'ᾈ',
'ᾉ' => 'ᾉ',
'ᾊ' => 'ᾊ',
'ᾋ' => 'ᾋ',
'ᾌ' => 'ᾌ',
'ᾍ' => 'ᾍ',
'ᾎ' => 'ᾎ',
'ᾏ' => 'ᾏ',
'ᾐ' => 'ᾐ',
'ᾑ' => 'ᾑ',
'ᾒ' => 'ᾒ',
'ᾓ' => 'ᾓ',
'ᾔ' => 'ᾔ',
'ᾕ' => 'ᾕ',
'ᾖ' => 'ᾖ',
'ᾗ' => 'ᾗ',
'ᾘ' => 'ᾘ',
'ᾙ' => 'ᾙ',
'ᾚ' => 'ᾚ',
'ᾛ' => 'ᾛ',
'ᾜ' => 'ᾜ',
'ᾝ' => 'ᾝ',
'ᾞ' => 'ᾞ',
'ᾟ' => 'ᾟ',
'ᾠ' => 'ᾠ',
'ᾡ' => 'ᾡ',
'ᾢ' => 'ᾢ',
'ᾣ' => 'ᾣ',
'ᾤ' => 'ᾤ',
'ᾥ' => 'ᾥ',
'ᾦ' => 'ᾦ',
'ᾧ' => 'ᾧ',
'ᾨ' => 'ᾨ',
'ᾩ' => 'ᾩ',
'ᾪ' => 'ᾪ',
'ᾫ' => 'ᾫ',
'ᾬ' => 'ᾬ',
'ᾭ' => 'ᾭ',
'ᾮ' => 'ᾮ',
'ᾯ' => 'ᾯ',
'ᾰ' => 'ᾰ',
'ᾱ' => 'ᾱ',
'ᾲ' => 'ᾲ',
'ᾳ' => 'ᾳ',
'ᾴ' => 'ᾴ',
'ᾶ' => 'ᾶ',
'ᾷ' => 'ᾷ',
'Ᾰ' => 'Ᾰ',
'Ᾱ' => 'Ᾱ',
'Ὰ' => 'Ὰ',
'ᾼ' => 'ᾼ',
'῁' => '῁',
'ῂ' => 'ῂ',
'ῃ' => 'ῃ',
'ῄ' => 'ῄ',
'ῆ' => 'ῆ',
'ῇ' => 'ῇ',
'Ὲ' => 'Ὲ',
'Ὴ' => 'Ὴ',
'ῌ' => 'ῌ',
'῍' => '῍',
'῎' => '῎',
'῏' => '῏',
'ῐ' => 'ῐ',
'ῑ' => 'ῑ',
'ῒ' => 'ῒ',
'ῖ' => 'ῖ',
'ῗ' => 'ῗ',
'Ῐ' => 'Ῐ',
'Ῑ' => 'Ῑ',
'Ὶ' => 'Ὶ',
'῝' => '῝',
'῞' => '῞',
'῟' => '῟',
'ῠ' => 'ῠ',
'ῡ' => 'ῡ',
'ῢ' => 'ῢ',
'ῤ' => 'ῤ',
'ῥ' => 'ῥ',
'ῦ' => 'ῦ',
'ῧ' => 'ῧ',
'Ῠ' => 'Ῠ',
'Ῡ' => 'Ῡ',
'Ὺ' => 'Ὺ',
'Ῥ' => 'Ῥ',
'῭' => '῭',
'ῲ' => 'ῲ',
'ῳ' => 'ῳ',
'ῴ' => 'ῴ',
'ῶ' => 'ῶ',
'ῷ' => 'ῷ',
'Ὸ' => 'Ὸ',
'Ὼ' => 'Ὼ',
'ῼ' => 'ῼ',
'↚' => '↚',
'↛' => '↛',
'↮' => '↮',
'⇍' => '⇍',
'⇎' => '⇎',
'⇏' => '⇏',
'∄' => '∄',
'∉' => '∉',
'∌' => '∌',
'∤' => '∤',
'∦' => '∦',
'≁' => '≁',
'≄' => '≄',
'≇' => '≇',
'≉' => '≉',
'≠' => '≠',
'≢' => '≢',
'≭' => '≭',
'≮' => '≮',
'≯' => '≯',
'≰' => '≰',
'≱' => '≱',
'≴' => '≴',
'≵' => '≵',
'≸' => '≸',
'≹' => '≹',
'⊀' => '⊀',
'⊁' => '⊁',
'⊄' => '⊄',
'⊅' => '⊅',
'⊈' => '⊈',
'⊉' => '⊉',
'⊬' => '⊬',
'⊭' => '⊭',
'⊮' => '⊮',
'⊯' => '⊯',
'⋠' => '⋠',
'⋡' => '⋡',
'⋢' => '⋢',
'⋣' => '⋣',
'⋪' => '⋪',
'⋫' => '⋫',
'⋬' => '⋬',
'⋭' => '⋭',
'が' => 'が',
'ぎ' => 'ぎ',
'ぐ' => 'ぐ',
'げ' => 'げ',
'ご' => 'ご',
'ざ' => 'ざ',
'じ' => 'じ',
'ず' => 'ず',
'ぜ' => 'ぜ',
'ぞ' => 'ぞ',
'だ' => 'だ',
'ぢ' => 'ぢ',
'づ' => 'づ',
'で' => 'で',
'ど' => 'ど',
'ば' => 'ば',
'ぱ' => 'ぱ',
'び' => 'び',
'ぴ' => 'ぴ',
'ぶ' => 'ぶ',
'ぷ' => 'ぷ',
'べ' => 'べ',
'ぺ' => 'ぺ',
'ぼ' => 'ぼ',
'ぽ' => 'ぽ',
'ゔ' => 'ゔ',
'ゞ' => 'ゞ',
'ガ' => 'ガ',
'ギ' => 'ギ',
'グ' => 'グ',
'ゲ' => 'ゲ',
'ゴ' => 'ゴ',
'ザ' => 'ザ',
'ジ' => 'ジ',
'ズ' => 'ズ',
'ゼ' => 'ゼ',
'ゾ' => 'ゾ',
'ダ' => 'ダ',
'ヂ' => 'ヂ',
'ヅ' => 'ヅ',
'デ' => 'デ',
'ド' => 'ド',
'バ' => 'バ',
'パ' => 'パ',
'ビ' => 'ビ',
'ピ' => 'ピ',
'ブ' => 'ブ',
'プ' => 'プ',
'ベ' => 'ベ',
'ペ' => 'ペ',
'ボ' => 'ボ',
'ポ' => 'ポ',
'ヴ' => 'ヴ',
'ヷ' => 'ヷ',
'ヸ' => 'ヸ',
'ヹ' => 'ヹ',
'ヺ' => 'ヺ',
'ヾ' => 'ヾ',
'𑂚' => '𑂚',
'𑂜' => '𑂜',
'𑂫' => '𑂫',
'𑄮' => '𑄮',
'𑄯' => '𑄯',
'𑍋' => '𑍋',
'𑍌' => '𑍌',
'𑒻' => '𑒻',
'𑒼' => '𑒼',
'𑒾' => '𑒾',
'𑖺' => '𑖺',
'𑖻' => '𑖻',
'𑤸' => '𑤸',
);
<?php
return array (
'À' => 'À',
'Á' => 'Á',
'Â' => 'Â',
'Ã' => 'Ã',
'Ä' => 'Ä',
'Å' => 'Å',
'Ç' => 'Ç',
'È' => 'È',
'É' => 'É',
'Ê' => 'Ê',
'Ë' => 'Ë',
'Ì' => 'Ì',
'Í' => 'Í',
'Î' => 'Î',
'Ï' => 'Ï',
'Ñ' => 'Ñ',
'Ò' => 'Ò',
'Ó' => 'Ó',
'Ô' => 'Ô',
'Õ' => 'Õ',
'Ö' => 'Ö',
'Ù' => 'Ù',
'Ú' => 'Ú',
'Û' => 'Û',
'Ü' => 'Ü',
'Ý' => 'Ý',
'à' => 'à',
'á' => 'á',
'â' => 'â',
'ã' => 'ã',
'ä' => 'ä',
'å' => 'å',
'ç' => 'ç',
'è' => 'è',
'é' => 'é',
'ê' => 'ê',
'ë' => 'ë',
'ì' => 'ì',
'í' => 'í',
'î' => 'î',
'ï' => 'ï',
'ñ' => 'ñ',
'ò' => 'ò',
'ó' => 'ó',
'ô' => 'ô',
'õ' => 'õ',
'ö' => 'ö',
'ù' => 'ù',
'ú' => 'ú',
'û' => 'û',
'ü' => 'ü',
'ý' => 'ý',
'ÿ' => 'ÿ',
'Ā' => 'Ā',
'ā' => 'ā',
'Ă' => 'Ă',
'ă' => 'ă',
'Ą' => 'Ą',
'ą' => 'ą',
'Ć' => 'Ć',
'ć' => 'ć',
'Ĉ' => 'Ĉ',
'ĉ' => 'ĉ',
'Ċ' => 'Ċ',
'ċ' => 'ċ',
'Č' => 'Č',
'č' => 'č',
'Ď' => 'Ď',
'ď' => 'ď',
'Ē' => 'Ē',
'ē' => 'ē',
'Ĕ' => 'Ĕ',
'ĕ' => 'ĕ',
'Ė' => 'Ė',
'ė' => 'ė',
'Ę' => 'Ę',
'ę' => 'ę',
'Ě' => 'Ě',
'ě' => 'ě',
'Ĝ' => 'Ĝ',
'ĝ' => 'ĝ',
'Ğ' => 'Ğ',
'ğ' => 'ğ',
'Ġ' => 'Ġ',
'ġ' => 'ġ',
'Ģ' => 'Ģ',
'ģ' => 'ģ',
'Ĥ' => 'Ĥ',
'ĥ' => 'ĥ',
'Ĩ' => 'Ĩ',
'ĩ' => 'ĩ',
'Ī' => 'Ī',
'ī' => 'ī',
'Ĭ' => 'Ĭ',
'ĭ' => 'ĭ',
'Į' => 'Į',
'į' => 'į',
'İ' => 'İ',
'Ĵ' => 'Ĵ',
'ĵ' => 'ĵ',
'Ķ' => 'Ķ',
'ķ' => 'ķ',
'Ĺ' => 'Ĺ',
'ĺ' => 'ĺ',
'Ļ' => 'Ļ',
'ļ' => 'ļ',
'Ľ' => 'Ľ',
'ľ' => 'ľ',
'Ń' => 'Ń',
'ń' => 'ń',
'Ņ' => 'Ņ',
'ņ' => 'ņ',
'Ň' => 'Ň',
'ň' => 'ň',
'Ō' => 'Ō',
'ō' => 'ō',
'Ŏ' => 'Ŏ',
'ŏ' => 'ŏ',
'Ő' => 'Ő',
'ő' => 'ő',
'Ŕ' => 'Ŕ',
'ŕ' => 'ŕ',
'Ŗ' => 'Ŗ',
'ŗ' => 'ŗ',
'Ř' => 'Ř',
'ř' => 'ř',
'Ś' => 'Ś',
'ś' => 'ś',
'Ŝ' => 'Ŝ',
'ŝ' => 'ŝ',
'Ş' => 'Ş',
'ş' => 'ş',
'Š' => 'Š',
'š' => 'š',
'Ţ' => 'Ţ',
'ţ' => 'ţ',
'Ť' => 'Ť',
'ť' => 'ť',
'Ũ' => 'Ũ',
'ũ' => 'ũ',
'Ū' => 'Ū',
'ū' => 'ū',
'Ŭ' => 'Ŭ',
'ŭ' => 'ŭ',
'Ů' => 'Ů',
'ů' => 'ů',
'Ű' => 'Ű',
'ű' => 'ű',
'Ų' => 'Ų',
'ų' => 'ų',
'Ŵ' => 'Ŵ',
'ŵ' => 'ŵ',
'Ŷ' => 'Ŷ',
'ŷ' => 'ŷ',
'Ÿ' => 'Ÿ',
'Ź' => 'Ź',
'ź' => 'ź',
'Ż' => 'Ż',
'ż' => 'ż',
'Ž' => 'Ž',
'ž' => 'ž',
'Ơ' => 'Ơ',
'ơ' => 'ơ',
'Ư' => 'Ư',
'ư' => 'ư',
'Ǎ' => 'Ǎ',
'ǎ' => 'ǎ',
'Ǐ' => 'Ǐ',
'ǐ' => 'ǐ',
'Ǒ' => 'Ǒ',
'ǒ' => 'ǒ',
'Ǔ' => 'Ǔ',
'ǔ' => 'ǔ',
'Ǖ' => 'Ǖ',
'ǖ' => 'ǖ',
'Ǘ' => 'Ǘ',
'ǘ' => 'ǘ',
'Ǚ' => 'Ǚ',
'ǚ' => 'ǚ',
'Ǜ' => 'Ǜ',
'ǜ' => 'ǜ',
'Ǟ' => 'Ǟ',
'ǟ' => 'ǟ',
'Ǡ' => 'Ǡ',
'ǡ' => 'ǡ',
'Ǣ' => 'Ǣ',
'ǣ' => 'ǣ',
'Ǧ' => 'Ǧ',
'ǧ' => 'ǧ',
'Ǩ' => 'Ǩ',
'ǩ' => 'ǩ',
'Ǫ' => 'Ǫ',
'ǫ' => 'ǫ',
'Ǭ' => 'Ǭ',
'ǭ' => 'ǭ',
'Ǯ' => 'Ǯ',
'ǯ' => 'ǯ',
'ǰ' => 'ǰ',
'Ǵ' => 'Ǵ',
'ǵ' => 'ǵ',
'Ǹ' => 'Ǹ',
'ǹ' => 'ǹ',
'Ǻ' => 'Ǻ',
'ǻ' => 'ǻ',
'Ǽ' => 'Ǽ',
'ǽ' => 'ǽ',
'Ǿ' => 'Ǿ',
'ǿ' => 'ǿ',
'Ȁ' => 'Ȁ',
'ȁ' => 'ȁ',
'Ȃ' => 'Ȃ',
'ȃ' => 'ȃ',
'Ȅ' => 'Ȅ',
'ȅ' => 'ȅ',
'Ȇ' => 'Ȇ',
'ȇ' => 'ȇ',
'Ȉ' => 'Ȉ',
'ȉ' => 'ȉ',
'Ȋ' => 'Ȋ',
'ȋ' => 'ȋ',
'Ȍ' => 'Ȍ',
'ȍ' => 'ȍ',
'Ȏ' => 'Ȏ',
'ȏ' => 'ȏ',
'Ȑ' => 'Ȑ',
'ȑ' => 'ȑ',
'Ȓ' => 'Ȓ',
'ȓ' => 'ȓ',
'Ȕ' => 'Ȕ',
'ȕ' => 'ȕ',
'Ȗ' => 'Ȗ',
'ȗ' => 'ȗ',
'Ș' => 'Ș',
'ș' => 'ș',
'Ț' => 'Ț',
'ț' => 'ț',
'Ȟ' => 'Ȟ',
'ȟ' => 'ȟ',
'Ȧ' => 'Ȧ',
'ȧ' => 'ȧ',
'Ȩ' => 'Ȩ',
'ȩ' => 'ȩ',
'Ȫ' => 'Ȫ',
'ȫ' => 'ȫ',
'Ȭ' => 'Ȭ',
'ȭ' => 'ȭ',
'Ȯ' => 'Ȯ',
'ȯ' => 'ȯ',
'Ȱ' => 'Ȱ',
'ȱ' => 'ȱ',
'Ȳ' => 'Ȳ',
'ȳ' => 'ȳ',
'̀' => '̀',
'́' => '́',
'̓' => '̓',
'̈́' => '̈́',
'ʹ' => 'ʹ',
';' => ';',
'΅' => '΅',
'Ά' => 'Ά',
'·' => '·',
'Έ' => 'Έ',
'Ή' => 'Ή',
'Ί' => 'Ί',
'Ό' => 'Ό',
'Ύ' => 'Ύ',
'Ώ' => 'Ώ',
'ΐ' => 'ΐ',
'Ϊ' => 'Ϊ',
'Ϋ' => 'Ϋ',
'ά' => 'ά',
'έ' => 'έ',
'ή' => 'ή',
'ί' => 'ί',
'ΰ' => 'ΰ',
'ϊ' => 'ϊ',
'ϋ' => 'ϋ',
'ό' => 'ό',
'ύ' => 'ύ',
'ώ' => 'ώ',
'ϓ' => 'ϓ',
'ϔ' => 'ϔ',
'Ѐ' => 'Ѐ',
'Ё' => 'Ё',
'Ѓ' => 'Ѓ',
'Ї' => 'Ї',
'Ќ' => 'Ќ',
'Ѝ' => 'Ѝ',
'Ў' => 'Ў',
'Й' => 'Й',
'й' => 'й',
'ѐ' => 'ѐ',
'ё' => 'ё',
'ѓ' => 'ѓ',
'ї' => 'ї',
'ќ' => 'ќ',
'ѝ' => 'ѝ',
'ў' => 'ў',
'Ѷ' => 'Ѷ',
'ѷ' => 'ѷ',
'Ӂ' => 'Ӂ',
'ӂ' => 'ӂ',
'Ӑ' => 'Ӑ',
'ӑ' => 'ӑ',
'Ӓ' => 'Ӓ',
'ӓ' => 'ӓ',
'Ӗ' => 'Ӗ',
'ӗ' => 'ӗ',
'Ӛ' => 'Ӛ',
'ӛ' => 'ӛ',
'Ӝ' => 'Ӝ',
'ӝ' => 'ӝ',
'Ӟ' => 'Ӟ',
'ӟ' => 'ӟ',
'Ӣ' => 'Ӣ',
'ӣ' => 'ӣ',
'Ӥ' => 'Ӥ',
'ӥ' => 'ӥ',
'Ӧ' => 'Ӧ',
'ӧ' => 'ӧ',
'Ӫ' => 'Ӫ',
'ӫ' => 'ӫ',
'Ӭ' => 'Ӭ',
'ӭ' => 'ӭ',
'Ӯ' => 'Ӯ',
'ӯ' => 'ӯ',
'Ӱ' => 'Ӱ',
'ӱ' => 'ӱ',
'Ӳ' => 'Ӳ',
'ӳ' => 'ӳ',
'Ӵ' => 'Ӵ',
'ӵ' => 'ӵ',
'Ӹ' => 'Ӹ',
'ӹ' => 'ӹ',
'آ' => 'آ',
'أ' => 'أ',
'ؤ' => 'ؤ',
'إ' => 'إ',
'ئ' => 'ئ',
'ۀ' => 'ۀ',
'ۂ' => 'ۂ',
'ۓ' => 'ۓ',
'ऩ' => 'ऩ',
'ऱ' => 'ऱ',
'ऴ' => 'ऴ',
'क़' => 'क़',
'ख़' => 'ख़',
'ग़' => 'ग़',
'ज़' => 'ज़',
'ड़' => 'ड़',
'ढ़' => 'ढ़',
'फ़' => 'फ़',
'य़' => 'य़',
'ো' => 'ো',
'ৌ' => 'ৌ',
'ড়' => 'ড়',
'ঢ়' => 'ঢ়',
'য়' => 'য়',
'ਲ਼' => 'ਲ਼',
'ਸ਼' => 'ਸ਼',
'ਖ਼' => 'ਖ਼',
'ਗ਼' => 'ਗ਼',
'ਜ਼' => 'ਜ਼',
'ਫ਼' => 'ਫ਼',
'ୈ' => 'ୈ',
'ୋ' => 'ୋ',
'ୌ' => 'ୌ',
'ଡ଼' => 'ଡ଼',
'ଢ଼' => 'ଢ଼',
'ஔ' => 'ஔ',
'ொ' => 'ொ',
'ோ' => 'ோ',
'ௌ' => 'ௌ',
'ై' => 'ై',
'ೀ' => 'ೀ',
'ೇ' => 'ೇ',
'ೈ' => 'ೈ',
'ೊ' => 'ೊ',
'ೋ' => 'ೋ',
'ൊ' => 'ൊ',
'ോ' => 'ോ',
'ൌ' => 'ൌ',
'ේ' => 'ේ',
'ො' => 'ො',
'ෝ' => 'ෝ',
'ෞ' => 'ෞ',
'གྷ' => 'གྷ',
'ཌྷ' => 'ཌྷ',
'དྷ' => 'དྷ',
'བྷ' => 'བྷ',
'ཛྷ' => 'ཛྷ',
'ཀྵ' => 'ཀྵ',
'ཱི' => 'ཱི',
'ཱུ' => 'ཱུ',
'ྲྀ' => 'ྲྀ',
'ླྀ' => 'ླྀ',
'ཱྀ' => 'ཱྀ',
'ྒྷ' => 'ྒྷ',
'ྜྷ' => 'ྜྷ',
'ྡྷ' => 'ྡྷ',
'ྦྷ' => 'ྦྷ',
'ྫྷ' => 'ྫྷ',
'ྐྵ' => 'ྐྵ',
'ဦ' => 'ဦ',
'ᬆ' => 'ᬆ',
'ᬈ' => 'ᬈ',
'ᬊ' => 'ᬊ',
'ᬌ' => 'ᬌ',
'ᬎ' => 'ᬎ',
'ᬒ' => 'ᬒ',
'ᬻ' => 'ᬻ',
'ᬽ' => 'ᬽ',
'ᭀ' => 'ᭀ',
'ᭁ' => 'ᭁ',
'ᭃ' => 'ᭃ',
'Ḁ' => 'Ḁ',
'ḁ' => 'ḁ',
'Ḃ' => 'Ḃ',
'ḃ' => 'ḃ',
'Ḅ' => 'Ḅ',
'ḅ' => 'ḅ',
'Ḇ' => 'Ḇ',
'ḇ' => 'ḇ',
'Ḉ' => 'Ḉ',
'ḉ' => 'ḉ',
'Ḋ' => 'Ḋ',
'ḋ' => 'ḋ',
'Ḍ' => 'Ḍ',
'ḍ' => 'ḍ',
'Ḏ' => 'Ḏ',
'ḏ' => 'ḏ',
'Ḑ' => 'Ḑ',
'ḑ' => 'ḑ',
'Ḓ' => 'Ḓ',
'ḓ' => 'ḓ',
'Ḕ' => 'Ḕ',
'ḕ' => 'ḕ',
'Ḗ' => 'Ḗ',
'ḗ' => 'ḗ',
'Ḙ' => 'Ḙ',
'ḙ' => 'ḙ',
'Ḛ' => 'Ḛ',
'ḛ' => 'ḛ',
'Ḝ' => 'Ḝ',
'ḝ' => 'ḝ',
'Ḟ' => 'Ḟ',
'ḟ' => 'ḟ',
'Ḡ' => 'Ḡ',
'ḡ' => 'ḡ',
'Ḣ' => 'Ḣ',
'ḣ' => 'ḣ',
'Ḥ' => 'Ḥ',
'ḥ' => 'ḥ',
'Ḧ' => 'Ḧ',
'ḧ' => 'ḧ',
'Ḩ' => 'Ḩ',
'ḩ' => 'ḩ',
'Ḫ' => 'Ḫ',
'ḫ' => 'ḫ',
'Ḭ' => 'Ḭ',
'ḭ' => 'ḭ',
'Ḯ' => 'Ḯ',
'ḯ' => 'ḯ',
'Ḱ' => 'Ḱ',
'ḱ' => 'ḱ',
'Ḳ' => 'Ḳ',
'ḳ' => 'ḳ',
'Ḵ' => 'Ḵ',
'ḵ' => 'ḵ',
'Ḷ' => 'Ḷ',
'ḷ' => 'ḷ',
'Ḹ' => 'Ḹ',
'ḹ' => 'ḹ',
'Ḻ' => 'Ḻ',
'ḻ' => 'ḻ',
'Ḽ' => 'Ḽ',
'ḽ' => 'ḽ',
'Ḿ' => 'Ḿ',
'ḿ' => 'ḿ',
'Ṁ' => 'Ṁ',
'ṁ' => 'ṁ',
'Ṃ' => 'Ṃ',
'ṃ' => 'ṃ',
'Ṅ' => 'Ṅ',
'ṅ' => 'ṅ',
'Ṇ' => 'Ṇ',
'ṇ' => 'ṇ',
'Ṉ' => 'Ṉ',
'ṉ' => 'ṉ',
'Ṋ' => 'Ṋ',
'ṋ' => 'ṋ',
'Ṍ' => 'Ṍ',
'ṍ' => 'ṍ',
'Ṏ' => 'Ṏ',
'ṏ' => 'ṏ',
'Ṑ' => 'Ṑ',
'ṑ' => 'ṑ',
'Ṓ' => 'Ṓ',
'ṓ' => 'ṓ',
'Ṕ' => 'Ṕ',
'ṕ' => 'ṕ',
'Ṗ' => 'Ṗ',
'ṗ' => 'ṗ',
'Ṙ' => 'Ṙ',
'ṙ' => 'ṙ',
'Ṛ' => 'Ṛ',
'ṛ' => 'ṛ',
'Ṝ' => 'Ṝ',
'ṝ' => 'ṝ',
'Ṟ' => 'Ṟ',
'ṟ' => 'ṟ',
'Ṡ' => 'Ṡ',
'ṡ' => 'ṡ',
'Ṣ' => 'Ṣ',
'ṣ' => 'ṣ',
'Ṥ' => 'Ṥ',
'ṥ' => 'ṥ',
'Ṧ' => 'Ṧ',
'ṧ' => 'ṧ',
'Ṩ' => 'Ṩ',
'ṩ' => 'ṩ',
'Ṫ' => 'Ṫ',
'ṫ' => 'ṫ',
'Ṭ' => 'Ṭ',
'ṭ' => 'ṭ',
'Ṯ' => 'Ṯ',
'ṯ' => 'ṯ',
'Ṱ' => 'Ṱ',
'ṱ' => 'ṱ',
'Ṳ' => 'Ṳ',
'ṳ' => 'ṳ',
'Ṵ' => 'Ṵ',
'ṵ' => 'ṵ',
'Ṷ' => 'Ṷ',
'ṷ' => 'ṷ',
'Ṹ' => 'Ṹ',
'ṹ' => 'ṹ',
'Ṻ' => 'Ṻ',
'ṻ' => 'ṻ',
'Ṽ' => 'Ṽ',
'ṽ' => 'ṽ',
'Ṿ' => 'Ṿ',
'ṿ' => 'ṿ',
'Ẁ' => 'Ẁ',
'ẁ' => 'ẁ',
'Ẃ' => 'Ẃ',
'ẃ' => 'ẃ',
'Ẅ' => 'Ẅ',
'ẅ' => 'ẅ',
'Ẇ' => 'Ẇ',
'ẇ' => 'ẇ',
'Ẉ' => 'Ẉ',
'ẉ' => 'ẉ',
'Ẋ' => 'Ẋ',
'ẋ' => 'ẋ',
'Ẍ' => 'Ẍ',
'ẍ' => 'ẍ',
'Ẏ' => 'Ẏ',
'ẏ' => 'ẏ',
'Ẑ' => 'Ẑ',
'ẑ' => 'ẑ',
'Ẓ' => 'Ẓ',
'ẓ' => 'ẓ',
'Ẕ' => 'Ẕ',
'ẕ' => 'ẕ',
'ẖ' => 'ẖ',
'ẗ' => 'ẗ',
'ẘ' => 'ẘ',
'ẙ' => 'ẙ',
'ẛ' => 'ẛ',
'Ạ' => 'Ạ',
'ạ' => 'ạ',
'Ả' => 'Ả',
'ả' => 'ả',
'Ấ' => 'Ấ',
'ấ' => 'ấ',
'Ầ' => 'Ầ',
'ầ' => 'ầ',
'Ẩ' => 'Ẩ',
'ẩ' => 'ẩ',
'Ẫ' => 'Ẫ',
'ẫ' => 'ẫ',
'Ậ' => 'Ậ',
'ậ' => 'ậ',
'Ắ' => 'Ắ',
'ắ' => 'ắ',
'Ằ' => 'Ằ',
'ằ' => 'ằ',
'Ẳ' => 'Ẳ',
'ẳ' => 'ẳ',
'Ẵ' => 'Ẵ',
'ẵ' => 'ẵ',
'Ặ' => 'Ặ',
'ặ' => 'ặ',
'Ẹ' => 'Ẹ',
'ẹ' => 'ẹ',
'Ẻ' => 'Ẻ',
'ẻ' => 'ẻ',
'Ẽ' => 'Ẽ',
'ẽ' => 'ẽ',
'Ế' => 'Ế',
'ế' => 'ế',
'Ề' => 'Ề',
'ề' => 'ề',
'Ể' => 'Ể',
'ể' => 'ể',
'Ễ' => 'Ễ',
'ễ' => 'ễ',
'Ệ' => 'Ệ',
'ệ' => 'ệ',
'Ỉ' => 'Ỉ',
'ỉ' => 'ỉ',
'Ị' => 'Ị',
'ị' => 'ị',
'Ọ' => 'Ọ',
'ọ' => 'ọ',
'Ỏ' => 'Ỏ',
'ỏ' => 'ỏ',
'Ố' => 'Ố',
'ố' => 'ố',
'Ồ' => 'Ồ',
'ồ' => 'ồ',
'Ổ' => 'Ổ',
'ổ' => 'ổ',
'Ỗ' => 'Ỗ',
'ỗ' => 'ỗ',
'Ộ' => 'Ộ',
'ộ' => 'ộ',
'Ớ' => 'Ớ',
'ớ' => 'ớ',
'Ờ' => 'Ờ',
'ờ' => 'ờ',
'Ở' => 'Ở',
'ở' => 'ở',
'Ỡ' => 'Ỡ',
'ỡ' => 'ỡ',
'Ợ' => 'Ợ',
'ợ' => 'ợ',
'Ụ' => 'Ụ',
'ụ' => 'ụ',
'Ủ' => 'Ủ',
'ủ' => 'ủ',
'Ứ' => 'Ứ',
'ứ' => 'ứ',
'Ừ' => 'Ừ',
'ừ' => 'ừ',
'Ử' => 'Ử',
'ử' => 'ử',
'Ữ' => 'Ữ',
'ữ' => 'ữ',
'Ự' => 'Ự',
'ự' => 'ự',
'Ỳ' => 'Ỳ',
'ỳ' => 'ỳ',
'Ỵ' => 'Ỵ',
'ỵ' => 'ỵ',
'Ỷ' => 'Ỷ',
'ỷ' => 'ỷ',
'Ỹ' => 'Ỹ',
'ỹ' => 'ỹ',
'ἀ' => 'ἀ',
'ἁ' => 'ἁ',
'ἂ' => 'ἂ',
'ἃ' => 'ἃ',
'ἄ' => 'ἄ',
'ἅ' => 'ἅ',
'ἆ' => 'ἆ',
'ἇ' => 'ἇ',
'Ἀ' => 'Ἀ',
'Ἁ' => 'Ἁ',
'Ἂ' => 'Ἂ',
'Ἃ' => 'Ἃ',
'Ἄ' => 'Ἄ',
'Ἅ' => 'Ἅ',
'Ἆ' => 'Ἆ',
'Ἇ' => 'Ἇ',
'ἐ' => 'ἐ',
'ἑ' => 'ἑ',
'ἒ' => 'ἒ',
'ἓ' => 'ἓ',
'ἔ' => 'ἔ',
'ἕ' => 'ἕ',
'Ἐ' => 'Ἐ',
'Ἑ' => 'Ἑ',
'Ἒ' => 'Ἒ',
'Ἓ' => 'Ἓ',
'Ἔ' => 'Ἔ',
'Ἕ' => 'Ἕ',
'ἠ' => 'ἠ',
'ἡ' => 'ἡ',
'ἢ' => 'ἢ',
'ἣ' => 'ἣ',
'ἤ' => 'ἤ',
'ἥ' => 'ἥ',
'ἦ' => 'ἦ',
'ἧ' => 'ἧ',
'Ἠ' => 'Ἠ',
'Ἡ' => 'Ἡ',
'Ἢ' => 'Ἢ',
'Ἣ' => 'Ἣ',
'Ἤ' => 'Ἤ',
'Ἥ' => 'Ἥ',
'Ἦ' => 'Ἦ',
'Ἧ' => 'Ἧ',
'ἰ' => 'ἰ',
'ἱ' => 'ἱ',
'ἲ' => 'ἲ',
'ἳ' => 'ἳ',
'ἴ' => 'ἴ',
'ἵ' => 'ἵ',
'ἶ' => 'ἶ',
'ἷ' => 'ἷ',
'Ἰ' => 'Ἰ',
'Ἱ' => 'Ἱ',
'Ἲ' => 'Ἲ',
'Ἳ' => 'Ἳ',
'Ἴ' => 'Ἴ',
'Ἵ' => 'Ἵ',
'Ἶ' => 'Ἶ',
'Ἷ' => 'Ἷ',
'ὀ' => 'ὀ',
'ὁ' => 'ὁ',
'ὂ' => 'ὂ',
'ὃ' => 'ὃ',
'ὄ' => 'ὄ',
'ὅ' => 'ὅ',
'Ὀ' => 'Ὀ',
'Ὁ' => 'Ὁ',
'Ὂ' => 'Ὂ',
'Ὃ' => 'Ὃ',
'Ὄ' => 'Ὄ',
'Ὅ' => 'Ὅ',
'ὐ' => 'ὐ',
'ὑ' => 'ὑ',
'ὒ' => 'ὒ',
'ὓ' => 'ὓ',
'ὔ' => 'ὔ',
'ὕ' => 'ὕ',
'ὖ' => 'ὖ',
'ὗ' => 'ὗ',
'Ὑ' => 'Ὑ',
'Ὓ' => 'Ὓ',
'Ὕ' => 'Ὕ',
'Ὗ' => 'Ὗ',
'ὠ' => 'ὠ',
'ὡ' => 'ὡ',
'ὢ' => 'ὢ',
'ὣ' => 'ὣ',
'ὤ' => 'ὤ',
'ὥ' => 'ὥ',
'ὦ' => 'ὦ',
'ὧ' => 'ὧ',
'Ὠ' => 'Ὠ',
'Ὡ' => 'Ὡ',
'Ὢ' => 'Ὢ',
'Ὣ' => 'Ὣ',
'Ὤ' => 'Ὤ',
'Ὥ' => 'Ὥ',
'Ὦ' => 'Ὦ',
'Ὧ' => 'Ὧ',
'ὰ' => 'ὰ',
'ά' => 'ά',
'ὲ' => 'ὲ',
'έ' => 'έ',
'ὴ' => 'ὴ',
'ή' => 'ή',
'ὶ' => 'ὶ',
'ί' => 'ί',
'ὸ' => 'ὸ',
'ό' => 'ό',
'ὺ' => 'ὺ',
'ύ' => 'ύ',
'ὼ' => 'ὼ',
'ώ' => 'ώ',
'ᾀ' => 'ᾀ',
'ᾁ' => 'ᾁ',
'ᾂ' => 'ᾂ',
'ᾃ' => 'ᾃ',
'ᾄ' => 'ᾄ',
'ᾅ' => 'ᾅ',
'ᾆ' => 'ᾆ',
'ᾇ' => 'ᾇ',
'ᾈ' => 'ᾈ',
'ᾉ' => 'ᾉ',
'ᾊ' => 'ᾊ',
'ᾋ' => 'ᾋ',
'ᾌ' => 'ᾌ',
'ᾍ' => 'ᾍ',
'ᾎ' => 'ᾎ',
'ᾏ' => 'ᾏ',
'ᾐ' => 'ᾐ',
'ᾑ' => 'ᾑ',
'ᾒ' => 'ᾒ',
'ᾓ' => 'ᾓ',
'ᾔ' => 'ᾔ',
'ᾕ' => 'ᾕ',
'ᾖ' => 'ᾖ',
'ᾗ' => 'ᾗ',
'ᾘ' => 'ᾘ',
'ᾙ' => 'ᾙ',
'ᾚ' => 'ᾚ',
'ᾛ' => 'ᾛ',
'ᾜ' => 'ᾜ',
'ᾝ' => 'ᾝ',
'ᾞ' => 'ᾞ',
'ᾟ' => 'ᾟ',
'ᾠ' => 'ᾠ',
'ᾡ' => 'ᾡ',
'ᾢ' => 'ᾢ',
'ᾣ' => 'ᾣ',
'ᾤ' => 'ᾤ',
'ᾥ' => 'ᾥ',
'ᾦ' => 'ᾦ',
'ᾧ' => 'ᾧ',
'ᾨ' => 'ᾨ',
'ᾩ' => 'ᾩ',
'ᾪ' => 'ᾪ',
'ᾫ' => 'ᾫ',
'ᾬ' => 'ᾬ',
'ᾭ' => 'ᾭ',
'ᾮ' => 'ᾮ',
'ᾯ' => 'ᾯ',
'ᾰ' => 'ᾰ',
'ᾱ' => 'ᾱ',
'ᾲ' => 'ᾲ',
'ᾳ' => 'ᾳ',
'ᾴ' => 'ᾴ',
'ᾶ' => 'ᾶ',
'ᾷ' => 'ᾷ',
'Ᾰ' => 'Ᾰ',
'Ᾱ' => 'Ᾱ',
'Ὰ' => 'Ὰ',
'Ά' => 'Ά',
'ᾼ' => 'ᾼ',
'' => 'ι',
'῁' => '῁',
'ῂ' => 'ῂ',
'ῃ' => 'ῃ',
'ῄ' => 'ῄ',
'ῆ' => 'ῆ',
'ῇ' => 'ῇ',
'Ὲ' => 'Ὲ',
'Έ' => 'Έ',
'Ὴ' => 'Ὴ',
'Ή' => 'Ή',
'ῌ' => 'ῌ',
'῍' => '῍',
'῎' => '῎',
'῏' => '῏',
'ῐ' => 'ῐ',
'ῑ' => 'ῑ',
'ῒ' => 'ῒ',
'ΐ' => 'ΐ',
'ῖ' => 'ῖ',
'ῗ' => 'ῗ',
'Ῐ' => 'Ῐ',
'Ῑ' => 'Ῑ',
'Ὶ' => 'Ὶ',
'Ί' => 'Ί',
'῝' => '῝',
'῞' => '῞',
'῟' => '῟',
'ῠ' => 'ῠ',
'ῡ' => 'ῡ',
'ῢ' => 'ῢ',
'ΰ' => 'ΰ',
'ῤ' => 'ῤ',
'ῥ' => 'ῥ',
'ῦ' => 'ῦ',
'ῧ' => 'ῧ',
'Ῠ' => 'Ῠ',
'Ῡ' => 'Ῡ',
'Ὺ' => 'Ὺ',
'Ύ' => 'Ύ',
'Ῥ' => 'Ῥ',
'῭' => '῭',
'΅' => '΅',
'' => '`',
'ῲ' => 'ῲ',
'ῳ' => 'ῳ',
'ῴ' => 'ῴ',
'ῶ' => 'ῶ',
'ῷ' => 'ῷ',
'Ὸ' => 'Ὸ',
'Ό' => 'Ό',
'Ὼ' => 'Ὼ',
'Ώ' => 'Ώ',
'ῼ' => 'ῼ',
'' => '´',
' ' => '',
'' => '',
'Ω' => 'Ω',
'' => 'K',
'Å' => 'Å',
'↚' => '↚',
'↛' => '↛',
'↮' => '↮',
'⇍' => '⇍',
'⇎' => '⇎',
'⇏' => '⇏',
'∄' => '∄',
'∉' => '∉',
'∌' => '∌',
'∤' => '∤',
'∦' => '∦',
'≁' => '≁',
'≄' => '≄',
'≇' => '≇',
'≉' => '≉',
'≠' => '≠',
'≢' => '≢',
'≭' => '≭',
'≮' => '≮',
'≯' => '≯',
'≰' => '≰',
'≱' => '≱',
'≴' => '≴',
'≵' => '≵',
'≸' => '≸',
'≹' => '≹',
'⊀' => '⊀',
'⊁' => '⊁',
'⊄' => '⊄',
'⊅' => '⊅',
'⊈' => '⊈',
'⊉' => '⊉',
'⊬' => '⊬',
'⊭' => '⊭',
'⊮' => '⊮',
'⊯' => '⊯',
'⋠' => '⋠',
'⋡' => '⋡',
'⋢' => '⋢',
'⋣' => '⋣',
'⋪' => '⋪',
'⋫' => '⋫',
'⋬' => '⋬',
'⋭' => '⋭',
'〈' => '〈',
'〉' => '〉',
'⫝̸' => '⫝̸',
'が' => 'が',
'ぎ' => 'ぎ',
'ぐ' => 'ぐ',
'げ' => 'げ',
'ご' => 'ご',
'ざ' => 'ざ',
'じ' => 'じ',
'ず' => 'ず',
'ぜ' => 'ぜ',
'ぞ' => 'ぞ',
'だ' => 'だ',
'ぢ' => 'ぢ',
'づ' => 'づ',
'で' => 'で',
'ど' => 'ど',
'ば' => 'ば',
'ぱ' => 'ぱ',
'び' => 'び',
'ぴ' => 'ぴ',
'ぶ' => 'ぶ',
'ぷ' => 'ぷ',
'べ' => 'べ',
'ぺ' => 'ぺ',
'ぼ' => 'ぼ',
'ぽ' => 'ぽ',
'ゔ' => 'ゔ',
'ゞ' => 'ゞ',
'ガ' => 'ガ',
'ギ' => 'ギ',
'グ' => 'グ',
'ゲ' => 'ゲ',
'ゴ' => 'ゴ',
'ザ' => 'ザ',
'ジ' => 'ジ',
'ズ' => 'ズ',
'ゼ' => 'ゼ',
'ゾ' => 'ゾ',
'ダ' => 'ダ',
'ヂ' => 'ヂ',
'ヅ' => 'ヅ',
'デ' => 'デ',
'ド' => 'ド',
'バ' => 'バ',
'パ' => 'パ',
'ビ' => 'ビ',
'ピ' => 'ピ',
'ブ' => 'ブ',
'プ' => 'プ',
'ベ' => 'ベ',
'ペ' => 'ペ',
'ボ' => 'ボ',
'ポ' => 'ポ',
'ヴ' => 'ヴ',
'ヷ' => 'ヷ',
'ヸ' => 'ヸ',
'ヹ' => 'ヹ',
'ヺ' => 'ヺ',
'ヾ' => 'ヾ',
'豈' => '豈',
'更' => '更',
'車' => '車',
'賈' => '賈',
'滑' => '滑',
'串' => '串',
'句' => '句',
'龜' => '龜',
'龜' => '龜',
'契' => '契',
'金' => '金',
'喇' => '喇',
'奈' => '奈',
'懶' => '懶',
'癩' => '癩',
'羅' => '羅',
'蘿' => '蘿',
'螺' => '螺',
'裸' => '裸',
'邏' => '邏',
'樂' => '樂',
'洛' => '洛',
'烙' => '烙',
'珞' => '珞',
'落' => '落',
'酪' => '酪',
'駱' => '駱',
'亂' => '亂',
'卵' => '卵',
'欄' => '欄',
'爛' => '爛',
'蘭' => '蘭',
'鸞' => '鸞',
'嵐' => '嵐',
'濫' => '濫',
'藍' => '藍',
'襤' => '襤',
'拉' => '拉',
'臘' => '臘',
'蠟' => '蠟',
'廊' => '廊',
'朗' => '朗',
'浪' => '浪',
'狼' => '狼',
'郎' => '郎',
'來' => '來',
'冷' => '冷',
'勞' => '勞',
'擄' => '擄',
'櫓' => '櫓',
'爐' => '爐',
'盧' => '盧',
'老' => '老',
'蘆' => '蘆',
'虜' => '虜',
'路' => '路',
'露' => '露',
'魯' => '魯',
'鷺' => '鷺',
'碌' => '碌',
'祿' => '祿',
'綠' => '綠',
'菉' => '菉',
'錄' => '錄',
'鹿' => '鹿',
'論' => '論',
'壟' => '壟',
'弄' => '弄',
'籠' => '籠',
'聾' => '聾',
'牢' => '牢',
'磊' => '磊',
'賂' => '賂',
'雷' => '雷',
'壘' => '壘',
'屢' => '屢',
'樓' => '樓',
'淚' => '淚',
'漏' => '漏',
'累' => '累',
'縷' => '縷',
'陋' => '陋',
'勒' => '勒',
'肋' => '肋',
'凜' => '凜',
'凌' => '凌',
'稜' => '稜',
'綾' => '綾',
'菱' => '菱',
'陵' => '陵',
'讀' => '讀',
'拏' => '拏',
'樂' => '樂',
'諾' => '諾',
'丹' => '丹',
'寧' => '寧',
'怒' => '怒',
'率' => '率',
'異' => '異',
'北' => '北',
'磻' => '磻',
'便' => '便',
'復' => '復',
'不' => '不',
'泌' => '泌',
'數' => '數',
'索' => '索',
'參' => '參',
'塞' => '塞',
'省' => '省',
'葉' => '葉',
'說' => '說',
'殺' => '殺',
'辰' => '辰',
'沈' => '沈',
'拾' => '拾',
'若' => '若',
'掠' => '掠',
'略' => '略',
'亮' => '亮',
'兩' => '兩',
'凉' => '凉',
'梁' => '梁',
'糧' => '糧',
'良' => '良',
'諒' => '諒',
'量' => '量',
'勵' => '勵',
'呂' => '呂',
'女' => '女',
'廬' => '廬',
'旅' => '旅',
'濾' => '濾',
'礪' => '礪',
'閭' => '閭',
'驪' => '驪',
'麗' => '麗',
'黎' => '黎',
'力' => '力',
'曆' => '曆',
'歷' => '歷',
'轢' => '轢',
'年' => '年',
'憐' => '憐',
'戀' => '戀',
'撚' => '撚',
'漣' => '漣',
'煉' => '煉',
'璉' => '璉',
'秊' => '秊',
'練' => '練',
'聯' => '聯',
'輦' => '輦',
'蓮' => '蓮',
'連' => '連',
'鍊' => '鍊',
'列' => '列',
'劣' => '劣',
'咽' => '咽',
'烈' => '烈',
'裂' => '裂',
'說' => '說',
'廉' => '廉',
'念' => '念',
'捻' => '捻',
'殮' => '殮',
'簾' => '簾',
'獵' => '獵',
'令' => '令',
'囹' => '囹',
'寧' => '寧',
'嶺' => '嶺',
'怜' => '怜',
'玲' => '玲',
'瑩' => '瑩',
'羚' => '羚',
'聆' => '聆',
'鈴' => '鈴',
'零' => '零',
'靈' => '靈',
'領' => '領',
'例' => '例',
'禮' => '禮',
'醴' => '醴',
'隸' => '隸',
'惡' => '惡',
'了' => '了',
'僚' => '僚',
'寮' => '寮',
'尿' => '尿',
'料' => '料',
'樂' => '樂',
'燎' => '燎',
'療' => '療',
'蓼' => '蓼',
'遼' => '遼',
'龍' => '龍',
'暈' => '暈',
'阮' => '阮',
'劉' => '劉',
'杻' => '杻',
'柳' => '柳',
'流' => '流',
'溜' => '溜',
'琉' => '琉',
'留' => '留',
'硫' => '硫',
'紐' => '紐',
'類' => '類',
'六' => '六',
'戮' => '戮',
'陸' => '陸',
'倫' => '倫',
'崙' => '崙',
'淪' => '淪',
'輪' => '輪',
'律' => '律',
'慄' => '慄',
'栗' => '栗',
'率' => '率',
'隆' => '隆',
'利' => '利',
'吏' => '吏',
'履' => '履',
'易' => '易',
'李' => '李',
'梨' => '梨',
'泥' => '泥',
'理' => '理',
'痢' => '痢',
'罹' => '罹',
'裏' => '裏',
'裡' => '裡',
'里' => '里',
'離' => '離',
'匿' => '匿',
'溺' => '溺',
'吝' => '吝',
'燐' => '燐',
'璘' => '璘',
'藺' => '藺',
'隣' => '隣',
'鱗' => '鱗',
'麟' => '麟',
'林' => '林',
'淋' => '淋',
'臨' => '臨',
'立' => '立',
'笠' => '笠',
'粒' => '粒',
'狀' => '狀',
'炙' => '炙',
'識' => '識',
'什' => '什',
'茶' => '茶',
'刺' => '刺',
'切' => '切',
'度' => '度',
'拓' => '拓',
'糖' => '糖',
'宅' => '宅',
'洞' => '洞',
'暴' => '暴',
'輻' => '輻',
'行' => '行',
'降' => '降',
'見' => '見',
'廓' => '廓',
'兀' => '兀',
'嗀' => '嗀',
'塚' => '塚',
'晴' => '晴',
'凞' => '凞',
'猪' => '猪',
'益' => '益',
'礼' => '礼',
'神' => '神',
'祥' => '祥',
'福' => '福',
'靖' => '靖',
'精' => '精',
'羽' => '羽',
'蘒' => '蘒',
'諸' => '諸',
'逸' => '逸',
'都' => '都',
'飯' => '飯',
'飼' => '飼',
'館' => '館',
'鶴' => '鶴',
'郞' => '郞',
'隷' => '隷',
'侮' => '侮',
'僧' => '僧',
'免' => '免',
'勉' => '勉',
'勤' => '勤',
'卑' => '卑',
'喝' => '喝',
'嘆' => '嘆',
'器' => '器',
'塀' => '塀',
'墨' => '墨',
'層' => '層',
'屮' => '屮',
'悔' => '悔',
'慨' => '慨',
'憎' => '憎',
'懲' => '懲',
'敏' => '敏',
'既' => '既',
'暑' => '暑',
'梅' => '梅',
'海' => '海',
'渚' => '渚',
'漢' => '漢',
'煮' => '煮',
'爫' => '爫',
'琢' => '琢',
'碑' => '碑',
'社' => '社',
'祉' => '祉',
'祈' => '祈',
'祐' => '祐',
'祖' => '祖',
'祝' => '祝',
'禍' => '禍',
'禎' => '禎',
'穀' => '穀',
'突' => '突',
'節' => '節',
'練' => '練',
'縉' => '縉',
'繁' => '繁',
'署' => '署',
'者' => '者',
'臭' => '臭',
'艹' => '艹',
'艹' => '艹',
'著' => '著',
'褐' => '褐',
'視' => '視',
'謁' => '謁',
'謹' => '謹',
'賓' => '賓',
'贈' => '贈',
'辶' => '辶',
'逸' => '逸',
'難' => '難',
'響' => '響',
'頻' => '頻',
'恵' => '恵',
'𤋮' => '𤋮',
'舘' => '舘',
'並' => '並',
'况' => '况',
'全' => '全',
'侀' => '侀',
'充' => '充',
'冀' => '冀',
'勇' => '勇',
'勺' => '勺',
'喝' => '喝',
'啕' => '啕',
'喙' => '喙',
'嗢' => '嗢',
'塚' => '塚',
'墳' => '墳',
'奄' => '奄',
'奔' => '奔',
'婢' => '婢',
'嬨' => '嬨',
'廒' => '廒',
'廙' => '廙',
'彩' => '彩',
'徭' => '徭',
'惘' => '惘',
'慎' => '慎',
'愈' => '愈',
'憎' => '憎',
'慠' => '慠',
'懲' => '懲',
'戴' => '戴',
'揄' => '揄',
'搜' => '搜',
'摒' => '摒',
'敖' => '敖',
'晴' => '晴',
'朗' => '朗',
'望' => '望',
'杖' => '杖',
'歹' => '歹',
'殺' => '殺',
'流' => '流',
'滛' => '滛',
'滋' => '滋',
'漢' => '漢',
'瀞' => '瀞',
'煮' => '煮',
'瞧' => '瞧',
'爵' => '爵',
'犯' => '犯',
'猪' => '猪',
'瑱' => '瑱',
'甆' => '甆',
'画' => '画',
'瘝' => '瘝',
'瘟' => '瘟',
'益' => '益',
'盛' => '盛',
'直' => '直',
'睊' => '睊',
'着' => '着',
'磌' => '磌',
'窱' => '窱',
'節' => '節',
'类' => '类',
'絛' => '絛',
'練' => '練',
'缾' => '缾',
'者' => '者',
'荒' => '荒',
'華' => '華',
'蝹' => '蝹',
'襁' => '襁',
'覆' => '覆',
'視' => '視',
'調' => '調',
'諸' => '諸',
'請' => '請',
'謁' => '謁',
'諾' => '諾',
'諭' => '諭',
'謹' => '謹',
'變' => '變',
'贈' => '贈',
'輸' => '輸',
'遲' => '遲',
'醙' => '醙',
'鉶' => '鉶',
'陼' => '陼',
'難' => '難',
'靖' => '靖',
'韛' => '韛',
'響' => '響',
'頋' => '頋',
'頻' => '頻',
'鬒' => '鬒',
'龜' => '龜',
'𢡊' => '𢡊',
'𢡄' => '𢡄',
'𣏕' => '𣏕',
'㮝' => '㮝',
'䀘' => '䀘',
'䀹' => '䀹',
'𥉉' => '𥉉',
'𥳐' => '𥳐',
'𧻓' => '𧻓',
'齃' => '齃',
'龎' => '龎',
'יִ' => 'יִ',
'ײַ' => 'ײַ',
'שׁ' => 'שׁ',
'שׂ' => 'שׂ',
'שּׁ' => 'שּׁ',
'שּׂ' => 'שּׂ',
'אַ' => 'אַ',
'אָ' => 'אָ',
'אּ' => 'אּ',
'בּ' => 'בּ',
'גּ' => 'גּ',
'דּ' => 'דּ',
'הּ' => 'הּ',
'וּ' => 'וּ',
'זּ' => 'זּ',
'טּ' => 'טּ',
'יּ' => 'יּ',
'ךּ' => 'ךּ',
'כּ' => 'כּ',
'לּ' => 'לּ',
'מּ' => 'מּ',
'נּ' => 'נּ',
'סּ' => 'סּ',
'ףּ' => 'ףּ',
'פּ' => 'פּ',
'צּ' => 'צּ',
'קּ' => 'קּ',
'רּ' => 'רּ',
'שּ' => 'שּ',
'תּ' => 'תּ',
'וֹ' => 'וֹ',
'בֿ' => 'בֿ',
'כֿ' => 'כֿ',
'פֿ' => 'פֿ',
'𑂚' => '𑂚',
'𑂜' => '𑂜',
'𑂫' => '𑂫',
'𑄮' => '𑄮',
'𑄯' => '𑄯',
'𑍋' => '𑍋',
'𑍌' => '𑍌',
'𑒻' => '𑒻',
'𑒼' => '𑒼',
'𑒾' => '𑒾',
'𑖺' => '𑖺',
'𑖻' => '𑖻',
'𑤸' => '𑤸',
'𝅗𝅥' => '𝅗𝅥',
'𝅘𝅥' => '𝅘𝅥',
'𝅘𝅥𝅮' => '𝅘𝅥𝅮',
'𝅘𝅥𝅯' => '𝅘𝅥𝅯',
'𝅘𝅥𝅰' => '𝅘𝅥𝅰',
'𝅘𝅥𝅱' => '𝅘𝅥𝅱',
'𝅘𝅥𝅲' => '𝅘𝅥𝅲',
'𝆹𝅥' => '𝆹𝅥',
'𝆺𝅥' => '𝆺𝅥',
'𝆹𝅥𝅮' => '𝆹𝅥𝅮',
'𝆺𝅥𝅮' => '𝆺𝅥𝅮',
'𝆹𝅥𝅯' => '𝆹𝅥𝅯',
'𝆺𝅥𝅯' => '𝆺𝅥𝅯',
'丽' => '丽',
'丸' => '丸',
'乁' => '乁',
'𠄢' => '𠄢',
'你' => '你',
'侮' => '侮',
'侻' => '侻',
'倂' => '倂',
'偺' => '偺',
'備' => '備',
'僧' => '僧',
'像' => '像',
'㒞' => '㒞',
'𠘺' => '𠘺',
'免' => '免',
'兔' => '兔',
'兤' => '兤',
'具' => '具',
'𠔜' => '𠔜',
'㒹' => '㒹',
'內' => '內',
'再' => '再',
'𠕋' => '𠕋',
'冗' => '冗',
'冤' => '冤',
'仌' => '仌',
'冬' => '冬',
'况' => '况',
'𩇟' => '𩇟',
'凵' => '凵',
'刃' => '刃',
'㓟' => '㓟',
'刻' => '刻',
'剆' => '剆',
'割' => '割',
'剷' => '剷',
'㔕' => '㔕',
'勇' => '勇',
'勉' => '勉',
'勤' => '勤',
'勺' => '勺',
'包' => '包',
'匆' => '匆',
'北' => '北',
'卉' => '卉',
'卑' => '卑',
'博' => '博',
'即' => '即',
'卽' => '卽',
'卿' => '卿',
'卿' => '卿',
'卿' => '卿',
'𠨬' => '𠨬',
'灰' => '灰',
'及' => '及',
'叟' => '叟',
'𠭣' => '𠭣',
'叫' => '叫',
'叱' => '叱',
'吆' => '吆',
'咞' => '咞',
'吸' => '吸',
'呈' => '呈',
'周' => '周',
'咢' => '咢',
'哶' => '哶',
'唐' => '唐',
'啓' => '啓',
'啣' => '啣',
'善' => '善',
'善' => '善',
'喙' => '喙',
'喫' => '喫',
'喳' => '喳',
'嗂' => '嗂',
'圖' => '圖',
'嘆' => '嘆',
'圗' => '圗',
'噑' => '噑',
'噴' => '噴',
'切' => '切',
'壮' => '壮',
'城' => '城',
'埴' => '埴',
'堍' => '堍',
'型' => '型',
'堲' => '堲',
'報' => '報',
'墬' => '墬',
'𡓤' => '𡓤',
'売' => '売',
'壷' => '壷',
'夆' => '夆',
'多' => '多',
'夢' => '夢',
'奢' => '奢',
'𡚨' => '𡚨',
'𡛪' => '𡛪',
'姬' => '姬',
'娛' => '娛',
'娧' => '娧',
'姘' => '姘',
'婦' => '婦',
'㛮' => '㛮',
'㛼' => '㛼',
'嬈' => '嬈',
'嬾' => '嬾',
'嬾' => '嬾',
'𡧈' => '𡧈',
'寃' => '寃',
'寘' => '寘',
'寧' => '寧',
'寳' => '寳',
'𡬘' => '𡬘',
'寿' => '寿',
'将' => '将',
'当' => '当',
'尢' => '尢',
'㞁' => '㞁',
'屠' => '屠',
'屮' => '屮',
'峀' => '峀',
'岍' => '岍',
'𡷤' => '𡷤',
'嵃' => '嵃',
'𡷦' => '𡷦',
'嵮' => '嵮',
'嵫' => '嵫',
'嵼' => '嵼',
'巡' => '巡',
'巢' => '巢',
'㠯' => '㠯',
'巽' => '巽',
'帨' => '帨',
'帽' => '帽',
'幩' => '幩',
'㡢' => '㡢',
'𢆃' => '𢆃',
'㡼' => '㡼',
'庰' => '庰',
'庳' => '庳',
'庶' => '庶',
'廊' => '廊',
'𪎒' => '𪎒',
'廾' => '廾',
'𢌱' => '𢌱',
'𢌱' => '𢌱',
'舁' => '舁',
'弢' => '弢',
'弢' => '弢',
'㣇' => '㣇',
'𣊸' => '𣊸',
'𦇚' => '𦇚',
'形' => '形',
'彫' => '彫',
'㣣' => '㣣',
'徚' => '徚',
'忍' => '忍',
'志' => '志',
'忹' => '忹',
'悁' => '悁',
'㤺' => '㤺',
'㤜' => '㤜',
'悔' => '悔',
'𢛔' => '𢛔',
'惇' => '惇',
'慈' => '慈',
'慌' => '慌',
'慎' => '慎',
'慌' => '慌',
'慺' => '慺',
'憎' => '憎',
'憲' => '憲',
'憤' => '憤',
'憯' => '憯',
'懞' => '懞',
'懲' => '懲',
'懶' => '懶',
'成' => '成',
'戛' => '戛',
'扝' => '扝',
'抱' => '抱',
'拔' => '拔',
'捐' => '捐',
'𢬌' => '𢬌',
'挽' => '挽',
'拼' => '拼',
'捨' => '捨',
'掃' => '掃',
'揤' => '揤',
'𢯱' => '𢯱',
'搢' => '搢',
'揅' => '揅',
'掩' => '掩',
'㨮' => '㨮',
'摩' => '摩',
'摾' => '摾',
'撝' => '撝',
'摷' => '摷',
'㩬' => '㩬',
'敏' => '敏',
'敬' => '敬',
'𣀊' => '𣀊',
'旣' => '旣',
'書' => '書',
'晉' => '晉',
'㬙' => '㬙',
'暑' => '暑',
'㬈' => '㬈',
'㫤' => '㫤',
'冒' => '冒',
'冕' => '冕',
'最' => '最',
'暜' => '暜',
'肭' => '肭',
'䏙' => '䏙',
'朗' => '朗',
'望' => '望',
'朡' => '朡',
'杞' => '杞',
'杓' => '杓',
'𣏃' => '𣏃',
'㭉' => '㭉',
'柺' => '柺',
'枅' => '枅',
'桒' => '桒',
'梅' => '梅',
'𣑭' => '𣑭',
'梎' => '梎',
'栟' => '栟',
'椔' => '椔',
'㮝' => '㮝',
'楂' => '楂',
'榣' => '榣',
'槪' => '槪',
'檨' => '檨',
'𣚣' => '𣚣',
'櫛' => '櫛',
'㰘' => '㰘',
'次' => '次',
'𣢧' => '𣢧',
'歔' => '歔',
'㱎' => '㱎',
'歲' => '歲',
'殟' => '殟',
'殺' => '殺',
'殻' => '殻',
'𣪍' => '𣪍',
'𡴋' => '𡴋',
'𣫺' => '𣫺',
'汎' => '汎',
'𣲼' => '𣲼',
'沿' => '沿',
'泍' => '泍',
'汧' => '汧',
'洖' => '洖',
'派' => '派',
'海' => '海',
'流' => '流',
'浩' => '浩',
'浸' => '浸',
'涅' => '涅',
'𣴞' => '𣴞',
'洴' => '洴',
'港' => '港',
'湮' => '湮',
'㴳' => '㴳',
'滋' => '滋',
'滇' => '滇',
'𣻑' => '𣻑',
'淹' => '淹',
'潮' => '潮',
'𣽞' => '𣽞',
'𣾎' => '𣾎',
'濆' => '濆',
'瀹' => '瀹',
'瀞' => '瀞',
'瀛' => '瀛',
'㶖' => '㶖',
'灊' => '灊',
'災' => '災',
'灷' => '灷',
'炭' => '炭',
'𠔥' => '𠔥',
'煅' => '煅',
'𤉣' => '𤉣',
'熜' => '熜',
'𤎫' => '𤎫',
'爨' => '爨',
'爵' => '爵',
'牐' => '牐',
'𤘈' => '𤘈',
'犀' => '犀',
'犕' => '犕',
'𤜵' => '𤜵',
'𤠔' => '𤠔',
'獺' => '獺',
'王' => '王',
'㺬' => '㺬',
'玥' => '玥',
'㺸' => '㺸',
'㺸' => '㺸',
'瑇' => '瑇',
'瑜' => '瑜',
'瑱' => '瑱',
'璅' => '璅',
'瓊' => '瓊',
'㼛' => '㼛',
'甤' => '甤',
'𤰶' => '𤰶',
'甾' => '甾',
'𤲒' => '𤲒',
'異' => '異',
'𢆟' => '𢆟',
'瘐' => '瘐',
'𤾡' => '𤾡',
'𤾸' => '𤾸',
'𥁄' => '𥁄',
'㿼' => '㿼',
'䀈' => '䀈',
'直' => '直',
'𥃳' => '𥃳',
'𥃲' => '𥃲',
'𥄙' => '𥄙',
'𥄳' => '𥄳',
'眞' => '眞',
'真' => '真',
'真' => '真',
'睊' => '睊',
'䀹' => '䀹',
'瞋' => '瞋',
'䁆' => '䁆',
'䂖' => '䂖',
'𥐝' => '𥐝',
'硎' => '硎',
'碌' => '碌',
'磌' => '磌',
'䃣' => '䃣',
'𥘦' => '𥘦',
'祖' => '祖',
'𥚚' => '𥚚',
'𥛅' => '𥛅',
'福' => '福',
'秫' => '秫',
'䄯' => '䄯',
'穀' => '穀',
'穊' => '穊',
'穏' => '穏',
'𥥼' => '𥥼',
'𥪧' => '𥪧',
'𥪧' => '𥪧',
'竮' => '竮',
'䈂' => '䈂',
'𥮫' => '𥮫',
'篆' => '篆',
'築' => '築',
'䈧' => '䈧',
'𥲀' => '𥲀',
'糒' => '糒',
'䊠' => '䊠',
'糨' => '糨',
'糣' => '糣',
'紀' => '紀',
'𥾆' => '𥾆',
'絣' => '絣',
'䌁' => '䌁',
'緇' => '緇',
'縂' => '縂',
'繅' => '繅',
'䌴' => '䌴',
'𦈨' => '𦈨',
'𦉇' => '𦉇',
'䍙' => '䍙',
'𦋙' => '𦋙',
'罺' => '罺',
'𦌾' => '𦌾',
'羕' => '羕',
'翺' => '翺',
'者' => '者',
'𦓚' => '𦓚',
'𦔣' => '𦔣',
'聠' => '聠',
'𦖨' => '𦖨',
'聰' => '聰',
'𣍟' => '𣍟',
'䏕' => '䏕',
'育' => '育',
'脃' => '脃',
'䐋' => '䐋',
'脾' => '脾',
'媵' => '媵',
'𦞧' => '𦞧',
'𦞵' => '𦞵',
'𣎓' => '𣎓',
'𣎜' => '𣎜',
'舁' => '舁',
'舄' => '舄',
'辞' => '辞',
'䑫' => '䑫',
'芑' => '芑',
'芋' => '芋',
'芝' => '芝',
'劳' => '劳',
'花' => '花',
'芳' => '芳',
'芽' => '芽',
'苦' => '苦',
'𦬼' => '𦬼',
'若' => '若',
'茝' => '茝',
'荣' => '荣',
'莭' => '莭',
'茣' => '茣',
'莽' => '莽',
'菧' => '菧',
'著' => '著',
'荓' => '荓',
'菊' => '菊',
'菌' => '菌',
'菜' => '菜',
'𦰶' => '𦰶',
'𦵫' => '𦵫',
'𦳕' => '𦳕',
'䔫' => '䔫',
'蓱' => '蓱',
'蓳' => '蓳',
'蔖' => '蔖',
'𧏊' => '𧏊',
'蕤' => '蕤',
'𦼬' => '𦼬',
'䕝' => '䕝',
'䕡' => '䕡',
'𦾱' => '𦾱',
'𧃒' => '𧃒',
'䕫' => '䕫',
'虐' => '虐',
'虜' => '虜',
'虧' => '虧',
'虩' => '虩',
'蚩' => '蚩',
'蚈' => '蚈',
'蜎' => '蜎',
'蛢' => '蛢',
'蝹' => '蝹',
'蜨' => '蜨',
'蝫' => '蝫',
'螆' => '螆',
'䗗' => '䗗',
'蟡' => '蟡',
'蠁' => '蠁',
'䗹' => '䗹',
'衠' => '衠',
'衣' => '衣',
'𧙧' => '𧙧',
'裗' => '裗',
'裞' => '裞',
'䘵' => '䘵',
'裺' => '裺',
'㒻' => '㒻',
'𧢮' => '𧢮',
'𧥦' => '𧥦',
'䚾' => '䚾',
'䛇' => '䛇',
'誠' => '誠',
'諭' => '諭',
'變' => '變',
'豕' => '豕',
'𧲨' => '𧲨',
'貫' => '貫',
'賁' => '賁',
'贛' => '贛',
'起' => '起',
'𧼯' => '𧼯',
'𠠄' => '𠠄',
'跋' => '跋',
'趼' => '趼',
'跰' => '跰',
'𠣞' => '𠣞',
'軔' => '軔',
'輸' => '輸',
'𨗒' => '𨗒',
'𨗭' => '𨗭',
'邔' => '邔',
'郱' => '郱',
'鄑' => '鄑',
'𨜮' => '𨜮',
'鄛' => '鄛',
'鈸' => '鈸',
'鋗' => '鋗',
'鋘' => '鋘',
'鉼' => '鉼',
'鏹' => '鏹',
'鐕' => '鐕',
'𨯺' => '𨯺',
'開' => '開',
'䦕' => '䦕',
'閷' => '閷',
'𨵷' => '𨵷',
'䧦' => '䧦',
'雃' => '雃',
'嶲' => '嶲',
'霣' => '霣',
'𩅅' => '𩅅',
'𩈚' => '𩈚',
'䩮' => '䩮',
'䩶' => '䩶',
'韠' => '韠',
'𩐊' => '𩐊',
'䪲' => '䪲',
'𩒖' => '𩒖',
'頋' => '頋',
'頋' => '頋',
'頩' => '頩',
'𩖶' => '𩖶',
'飢' => '飢',
'䬳' => '䬳',
'餩' => '餩',
'馧' => '馧',
'駂' => '駂',
'駾' => '駾',
'䯎' => '䯎',
'𩬰' => '𩬰',
'鬒' => '鬒',
'鱀' => '鱀',
'鳽' => '鳽',
'䳎' => '䳎',
'䳭' => '䳭',
'鵧' => '鵧',
'𪃎' => '𪃎',
'䳸' => '䳸',
'𪄅' => '𪄅',
'𪈎' => '𪈎',
'𪊑' => '𪊑',
'麻' => '麻',
'䵖' => '䵖',
'黹' => '黹',
'黾' => '黾',
'鼅' => '鼅',
'鼏' => '鼏',
'鼖' => '鼖',
'鼻' => '鼻',
'𪘀' => '𪘀',
);
<?php
return array (
' ' => ' ',
'¨' => ' ̈',
'ª' => 'a',
'¯' => ' ̄',
'²' => '2',
'³' => '3',
'´' => ' ́',
'µ' => 'μ',
'¸' => ' ̧',
'¹' => '1',
'º' => 'o',
'¼' => '14',
'½' => '12',
'¾' => '34',
'IJ' => 'IJ',
'ij' => 'ij',
'Ŀ' => 'L·',
'ŀ' => 'l·',
'ʼn' => 'ʼn',
'ſ' => 's',
'DŽ' => 'DŽ',
'Dž' => 'Dž',
'dž' => 'dž',
'LJ' => 'LJ',
'Lj' => 'Lj',
'lj' => 'lj',
'NJ' => 'NJ',
'Nj' => 'Nj',
'nj' => 'nj',
'DZ' => 'DZ',
'Dz' => 'Dz',
'dz' => 'dz',
'ʰ' => 'h',
'ʱ' => 'ɦ',
'ʲ' => 'j',
'ʳ' => 'r',
'ʴ' => 'ɹ',
'ʵ' => 'ɻ',
'ʶ' => 'ʁ',
'ʷ' => 'w',
'ʸ' => 'y',
'˘' => ' ̆',
'˙' => ' ̇',
'˚' => ' ̊',
'˛' => ' ̨',
'˜' => ' ̃',
'˝' => ' ̋',
'ˠ' => 'ɣ',
'ˡ' => 'l',
'ˢ' => 's',
'ˣ' => 'x',
'ˤ' => 'ʕ',
'ͺ' => ' ͅ',
'΄' => ' ́',
'΅' => ' ̈́',
'ϐ' => 'β',
'ϑ' => 'θ',
'ϒ' => 'Υ',
'ϓ' => 'Ύ',
'ϔ' => 'Ϋ',
'ϕ' => 'φ',
'ϖ' => 'π',
'ϰ' => 'κ',
'ϱ' => 'ρ',
'ϲ' => 'ς',
'ϴ' => 'Θ',
'ϵ' => 'ε',
'Ϲ' => 'Σ',
'և' => 'եւ',
'ٵ' => 'اٴ',
'ٶ' => 'وٴ',
'ٷ' => 'ۇٴ',
'ٸ' => 'يٴ',
'ำ' => 'ํา',
'ຳ' => 'ໍາ',
'ໜ' => 'ຫນ',
'ໝ' => 'ຫມ',
'༌' => '་',
'ཷ' => 'ྲཱྀ',
'ཹ' => 'ླཱྀ',
'ჼ' => 'ნ',
'ᴬ' => 'A',
'ᴭ' => 'Æ',
'ᴮ' => 'B',
'ᴰ' => 'D',
'ᴱ' => 'E',
'ᴲ' => 'Ǝ',
'ᴳ' => 'G',
'ᴴ' => 'H',
'ᴵ' => 'I',
'ᴶ' => 'J',
'ᴷ' => 'K',
'ᴸ' => 'L',
'ᴹ' => 'M',
'ᴺ' => 'N',
'ᴼ' => 'O',
'ᴽ' => 'Ȣ',
'ᴾ' => 'P',
'ᴿ' => 'R',
'ᵀ' => 'T',
'ᵁ' => 'U',
'ᵂ' => 'W',
'ᵃ' => 'a',
'ᵄ' => 'ɐ',
'ᵅ' => 'ɑ',
'ᵆ' => 'ᴂ',
'ᵇ' => 'b',
'ᵈ' => 'd',
'ᵉ' => 'e',
'ᵊ' => 'ə',
'ᵋ' => 'ɛ',
'ᵌ' => 'ɜ',
'ᵍ' => 'g',
'ᵏ' => 'k',
'ᵐ' => 'm',
'ᵑ' => 'ŋ',
'ᵒ' => 'o',
'ᵓ' => 'ɔ',
'ᵔ' => 'ᴖ',
'ᵕ' => 'ᴗ',
'ᵖ' => 'p',
'ᵗ' => 't',
'ᵘ' => 'u',
'ᵙ' => 'ᴝ',
'ᵚ' => 'ɯ',
'ᵛ' => 'v',
'ᵜ' => 'ᴥ',
'ᵝ' => 'β',
'ᵞ' => 'γ',
'ᵟ' => 'δ',
'ᵠ' => 'φ',
'ᵡ' => 'χ',
'ᵢ' => 'i',
'ᵣ' => 'r',
'ᵤ' => 'u',
'ᵥ' => 'v',
'ᵦ' => 'β',
'ᵧ' => 'γ',
'ᵨ' => 'ρ',
'ᵩ' => 'φ',
'ᵪ' => 'χ',
'ᵸ' => 'н',
'ᶛ' => 'ɒ',
'ᶜ' => 'c',
'ᶝ' => 'ɕ',
'ᶞ' => 'ð',
'ᶟ' => 'ɜ',
'ᶠ' => 'f',
'ᶡ' => 'ɟ',
'ᶢ' => 'ɡ',
'ᶣ' => 'ɥ',
'ᶤ' => 'ɨ',
'ᶥ' => 'ɩ',
'ᶦ' => 'ɪ',
'ᶧ' => 'ᵻ',
'ᶨ' => 'ʝ',
'ᶩ' => 'ɭ',
'ᶪ' => 'ᶅ',
'ᶫ' => 'ʟ',
'ᶬ' => 'ɱ',
'ᶭ' => 'ɰ',
'ᶮ' => 'ɲ',
'ᶯ' => 'ɳ',
'ᶰ' => 'ɴ',
'ᶱ' => 'ɵ',
'ᶲ' => 'ɸ',
'ᶳ' => 'ʂ',
'ᶴ' => 'ʃ',
'ᶵ' => 'ƫ',
'ᶶ' => 'ʉ',
'ᶷ' => 'ʊ',
'ᶸ' => '',
'ᶹ' => 'ʋ',
'ᶺ' => 'ʌ',
'ᶻ' => 'z',
'ᶼ' => 'ʐ',
'ᶽ' => 'ʑ',
'ᶾ' => 'ʒ',
'ᶿ' => 'θ',
'ẚ' => 'aʾ',
'ẛ' => 'ṡ',
'' => ' ̓',
'᾿' => ' ̓',
'' => ' ͂',
'῁' => ' ̈͂',
'῍' => ' ̓̀',
'῎' => ' ̓́',
'῏' => ' ̓͂',
'῝' => ' ̔̀',
'῞' => ' ̔́',
'῟' => ' ̔͂',
'῭' => ' ̈̀',
'΅' => ' ̈́',
'' => ' ́',
'' => ' ̔',
' ' => ' ',
'' => ' ',
'' => ' ',
'' => ' ',
'' => ' ',
'' => ' ',
'' => ' ',
'' => ' ',
'' => ' ',
'' => ' ',
'' => ' ',
'' => '',
'‗' => ' ̳',
'' => '.',
'‥' => '..',
'…' => '...',
'' => ' ',
'″' => '',
'‴' => '',
'‶' => '',
'‷' => '',
'‼' => '!!',
'‾' => ' ̅',
'⁇' => '??',
'⁈' => '?!',
'⁉' => '!?',
'⁗' => '',
'' => ' ',
'⁰' => '0',
'ⁱ' => 'i',
'⁴' => '4',
'⁵' => '5',
'⁶' => '6',
'⁷' => '7',
'⁸' => '8',
'⁹' => '9',
'⁺' => '+',
'⁻' => '',
'⁼' => '=',
'⁽' => '(',
'⁾' => ')',
'ⁿ' => 'n',
'₀' => '0',
'₁' => '1',
'₂' => '2',
'₃' => '3',
'₄' => '4',
'₅' => '5',
'₆' => '6',
'₇' => '7',
'₈' => '8',
'₉' => '9',
'₊' => '+',
'₋' => '',
'₌' => '=',
'₍' => '(',
'₎' => ')',
'ₐ' => 'a',
'ₑ' => 'e',
'ₒ' => 'o',
'ₓ' => 'x',
'ₔ' => 'ə',
'ₕ' => 'h',
'ₖ' => 'k',
'ₗ' => 'l',
'ₘ' => 'm',
'ₙ' => 'n',
'ₚ' => 'p',
'ₛ' => 's',
'ₜ' => 't',
'₨' => 'Rs',
'℀' => 'a/c',
'℁' => 'a/s',
'' => 'C',
'℃' => '°C',
'℅' => 'c/o',
'℆' => 'c/u',
'ℇ' => 'Ɛ',
'℉' => '°F',
'' => 'g',
'' => 'H',
'' => 'H',
'' => 'H',
'' => 'h',
'ℏ' => 'ħ',
'' => 'I',
'' => 'I',
'' => 'L',
'' => 'l',
'' => 'N',
'№' => 'No',
'' => 'P',
'' => 'Q',
'' => 'R',
'' => 'R',
'' => 'R',
'℠' => 'SM',
'℡' => 'TEL',
'™' => 'TM',
'' => 'Z',
'' => 'Z',
'' => 'B',
'' => 'C',
'' => 'e',
'' => 'E',
'' => 'F',
'' => 'M',
'' => 'o',
'ℵ' => 'א',
'ℶ' => 'ב',
'ℷ' => 'ג',
'ℸ' => 'ד',
'' => 'i',
'℻' => 'FAX',
'ℼ' => 'π',
'' => 'γ',
'ℾ' => 'Γ',
'ℿ' => 'Π',
'⅀' => '∑',
'' => 'D',
'' => 'd',
'' => 'e',
'' => 'i',
'' => 'j',
'⅐' => '17',
'⅑' => '19',
'⅒' => '110',
'⅓' => '13',
'⅔' => '23',
'⅕' => '15',
'⅖' => '25',
'⅗' => '35',
'⅘' => '45',
'⅙' => '16',
'⅚' => '56',
'⅛' => '18',
'⅜' => '38',
'⅝' => '58',
'⅞' => '78',
'⅟' => '1',
'' => 'I',
'Ⅱ' => 'II',
'Ⅲ' => 'III',
'Ⅳ' => 'IV',
'' => 'V',
'Ⅵ' => 'VI',
'Ⅶ' => 'VII',
'Ⅷ' => 'VIII',
'Ⅸ' => 'IX',
'' => 'X',
'Ⅺ' => 'XI',
'Ⅻ' => 'XII',
'' => 'L',
'' => 'C',
'' => 'D',
'' => 'M',
'' => 'i',
'ⅱ' => 'ii',
'ⅲ' => 'iii',
'ⅳ' => 'iv',
'' => 'v',
'ⅵ' => 'vi',
'ⅶ' => 'vii',
'ⅷ' => 'viii',
'ⅸ' => 'ix',
'' => 'x',
'ⅺ' => 'xi',
'ⅻ' => 'xii',
'' => 'l',
'' => 'c',
'' => 'd',
'ⅿ' => 'm',
'↉' => '03',
'∬' => '∫∫',
'∭' => '∫∫∫',
'∯' => '∮∮',
'∰' => '∮∮∮',
'①' => '1',
'②' => '2',
'③' => '3',
'④' => '4',
'⑤' => '5',
'⑥' => '6',
'⑦' => '7',
'⑧' => '8',
'⑨' => '9',
'⑩' => '10',
'⑪' => '11',
'⑫' => '12',
'⑬' => '13',
'⑭' => '14',
'⑮' => '15',
'⑯' => '16',
'⑰' => '17',
'⑱' => '18',
'⑲' => '19',
'⑳' => '20',
'⑴' => '(1)',
'⑵' => '(2)',
'⑶' => '(3)',
'⑷' => '(4)',
'⑸' => '(5)',
'⑹' => '(6)',
'⑺' => '(7)',
'⑻' => '(8)',
'⑼' => '(9)',
'⑽' => '(10)',
'⑾' => '(11)',
'⑿' => '(12)',
'⒀' => '(13)',
'⒁' => '(14)',
'⒂' => '(15)',
'⒃' => '(16)',
'⒄' => '(17)',
'⒅' => '(18)',
'⒆' => '(19)',
'⒇' => '(20)',
'⒈' => '1.',
'⒉' => '2.',
'⒊' => '3.',
'⒋' => '4.',
'⒌' => '5.',
'⒍' => '6.',
'⒎' => '7.',
'⒏' => '8.',
'⒐' => '9.',
'⒑' => '10.',
'⒒' => '11.',
'⒓' => '12.',
'⒔' => '13.',
'⒕' => '14.',
'⒖' => '15.',
'⒗' => '16.',
'⒘' => '17.',
'⒙' => '18.',
'⒚' => '19.',
'⒛' => '20.',
'⒜' => '(a)',
'⒝' => '(b)',
'⒞' => '(c)',
'⒟' => '(d)',
'⒠' => '(e)',
'⒡' => '(f)',
'⒢' => '(g)',
'⒣' => '(h)',
'⒤' => '(i)',
'⒥' => '(j)',
'⒦' => '(k)',
'⒧' => '(l)',
'⒨' => '(m)',
'⒩' => '(n)',
'⒪' => '(o)',
'⒫' => '(p)',
'⒬' => '(q)',
'⒭' => '(r)',
'⒮' => '(s)',
'⒯' => '(t)',
'⒰' => '(u)',
'⒱' => '(v)',
'⒲' => '(w)',
'⒳' => '(x)',
'⒴' => '(y)',
'⒵' => '(z)',
'Ⓐ' => 'A',
'Ⓑ' => 'B',
'Ⓒ' => 'C',
'Ⓓ' => 'D',
'Ⓔ' => 'E',
'Ⓕ' => 'F',
'Ⓖ' => 'G',
'Ⓗ' => 'H',
'Ⓘ' => 'I',
'Ⓙ' => 'J',
'Ⓚ' => 'K',
'Ⓛ' => 'L',
'Ⓜ' => 'M',
'Ⓝ' => 'N',
'Ⓞ' => 'O',
'Ⓟ' => 'P',
'Ⓠ' => 'Q',
'Ⓡ' => 'R',
'Ⓢ' => 'S',
'Ⓣ' => 'T',
'Ⓤ' => 'U',
'Ⓥ' => 'V',
'Ⓦ' => 'W',
'Ⓧ' => 'X',
'Ⓨ' => 'Y',
'Ⓩ' => 'Z',
'ⓐ' => 'a',
'ⓑ' => 'b',
'ⓒ' => 'c',
'ⓓ' => 'd',
'ⓔ' => 'e',
'ⓕ' => 'f',
'ⓖ' => 'g',
'ⓗ' => 'h',
'ⓘ' => 'i',
'ⓙ' => 'j',
'ⓚ' => 'k',
'ⓛ' => 'l',
'ⓜ' => 'm',
'ⓝ' => 'n',
'ⓞ' => 'o',
'ⓟ' => 'p',
'ⓠ' => 'q',
'ⓡ' => 'r',
'ⓢ' => 's',
'ⓣ' => 't',
'ⓤ' => 'u',
'ⓥ' => 'v',
'ⓦ' => 'w',
'ⓧ' => 'x',
'ⓨ' => 'y',
'ⓩ' => 'z',
'⓪' => '0',
'⨌' => '∫∫∫∫',
'⩴' => '::=',
'⩵' => '==',
'⩶' => '===',
'ⱼ' => 'j',
'ⱽ' => 'V',
'ⵯ' => 'ⵡ',
'⺟' => '母',
'⻳' => '龟',
'⼀' => '一',
'⼁' => '丨',
'' => '',
'' => '丿',
'⼄' => '乙',
'⼅' => '亅',
'⼆' => '二',
'⼇' => '亠',
'⼈' => '人',
'⼉' => '儿',
'⼊' => '入',
'⼋' => '八',
'⼌' => '冂',
'⼍' => '冖',
'⼎' => '冫',
'⼏' => '几',
'⼐' => '凵',
'⼑' => '刀',
'⼒' => '力',
'⼓' => '勹',
'⼔' => '匕',
'⼕' => '匚',
'⼖' => '匸',
'⼗' => '十',
'⼘' => '卜',
'⼙' => '卩',
'⼚' => '厂',
'⼛' => '厶',
'⼜' => '又',
'⼝' => '口',
'⼞' => '囗',
'⼟' => '土',
'⼠' => '士',
'⼡' => '夂',
'⼢' => '夊',
'⼣' => '夕',
'⼤' => '大',
'⼥' => '女',
'⼦' => '子',
'⼧' => '宀',
'⼨' => '寸',
'⼩' => '小',
'⼪' => '尢',
'⼫' => '尸',
'⼬' => '屮',
'⼭' => '山',
'⼮' => '巛',
'⼯' => '工',
'⼰' => '己',
'⼱' => '巾',
'⼲' => '干',
'⼳' => '幺',
'⼴' => '广',
'⼵' => '廴',
'⼶' => '廾',
'⼷' => '弋',
'⼸' => '弓',
'⼹' => '彐',
'⼺' => '彡',
'⼻' => '彳',
'⼼' => '心',
'⼽' => '戈',
'⼾' => '戶',
'⼿' => '手',
'⽀' => '支',
'⽁' => '攴',
'⽂' => '文',
'⽃' => '斗',
'⽄' => '斤',
'⽅' => '方',
'⽆' => '无',
'⽇' => '日',
'⽈' => '曰',
'⽉' => '月',
'⽊' => '木',
'⽋' => '欠',
'⽌' => '止',
'⽍' => '歹',
'⽎' => '殳',
'⽏' => '毋',
'⽐' => '比',
'⽑' => '毛',
'⽒' => '氏',
'⽓' => '气',
'⽔' => '水',
'⽕' => '火',
'⽖' => '爪',
'⽗' => '父',
'⽘' => '爻',
'⽙' => '爿',
'⽚' => '片',
'⽛' => '牙',
'⽜' => '牛',
'⽝' => '犬',
'⽞' => '玄',
'⽟' => '玉',
'⽠' => '瓜',
'⽡' => '瓦',
'⽢' => '甘',
'⽣' => '生',
'⽤' => '用',
'⽥' => '田',
'⽦' => '疋',
'⽧' => '疒',
'⽨' => '癶',
'⽩' => '白',
'⽪' => '皮',
'⽫' => '皿',
'⽬' => '目',
'⽭' => '矛',
'⽮' => '矢',
'⽯' => '石',
'⽰' => '示',
'⽱' => '禸',
'⽲' => '禾',
'⽳' => '穴',
'⽴' => '立',
'⽵' => '竹',
'⽶' => '米',
'⽷' => '糸',
'⽸' => '缶',
'⽹' => '网',
'⽺' => '羊',
'⽻' => '羽',
'⽼' => '老',
'⽽' => '而',
'⽾' => '耒',
'⽿' => '耳',
'⾀' => '聿',
'⾁' => '肉',
'⾂' => '臣',
'⾃' => '自',
'⾄' => '至',
'⾅' => '臼',
'⾆' => '舌',
'⾇' => '舛',
'⾈' => '舟',
'⾉' => '艮',
'⾊' => '色',
'⾋' => '艸',
'⾌' => '虍',
'⾍' => '虫',
'⾎' => '血',
'⾏' => '行',
'⾐' => '衣',
'⾑' => '襾',
'⾒' => '見',
'⾓' => '角',
'⾔' => '言',
'⾕' => '谷',
'⾖' => '豆',
'⾗' => '豕',
'⾘' => '豸',
'⾙' => '貝',
'⾚' => '赤',
'⾛' => '走',
'⾜' => '足',
'⾝' => '身',
'⾞' => '車',
'⾟' => '辛',
'⾠' => '辰',
'⾡' => '辵',
'⾢' => '邑',
'⾣' => '酉',
'⾤' => '釆',
'⾥' => '里',
'⾦' => '金',
'⾧' => '長',
'⾨' => '門',
'⾩' => '阜',
'⾪' => '隶',
'⾫' => '隹',
'⾬' => '雨',
'⾭' => '靑',
'⾮' => '非',
'⾯' => '面',
'⾰' => '革',
'⾱' => '韋',
'⾲' => '韭',
'⾳' => '音',
'⾴' => '頁',
'⾵' => '風',
'⾶' => '飛',
'⾷' => '食',
'⾸' => '首',
'⾹' => '香',
'⾺' => '馬',
'⾻' => '骨',
'⾼' => '高',
'⾽' => '髟',
'⾾' => '鬥',
'⾿' => '鬯',
'⿀' => '鬲',
'⿁' => '鬼',
'⿂' => '魚',
'⿃' => '鳥',
'⿄' => '鹵',
'⿅' => '鹿',
'⿆' => '麥',
'⿇' => '麻',
'⿈' => '黃',
'⿉' => '黍',
'⿊' => '黑',
'⿋' => '黹',
'⿌' => '黽',
'⿍' => '鼎',
'⿎' => '鼓',
'⿏' => '鼠',
'⿐' => '鼻',
'⿑' => '齊',
'⿒' => '齒',
'⿓' => '龍',
'⿔' => '龜',
'⿕' => '龠',
' ' => ' ',
'〶' => '〒',
'〸' => '十',
'〹' => '卄',
'〺' => '卅',
'゛' => ' ゙',
'゜' => ' ゚',
'ゟ' => 'より',
'ヿ' => 'コト',
'ㄱ' => 'ᄀ',
'ㄲ' => 'ᄁ',
'ㄳ' => 'ᆪ',
'ㄴ' => 'ᄂ',
'ㄵ' => 'ᆬ',
'ㄶ' => 'ᆭ',
'ㄷ' => 'ᄃ',
'ㄸ' => 'ᄄ',
'ㄹ' => 'ᄅ',
'ㄺ' => 'ᆰ',
'ㄻ' => 'ᆱ',
'ㄼ' => 'ᆲ',
'ㄽ' => 'ᆳ',
'ㄾ' => 'ᆴ',
'ㄿ' => 'ᆵ',
'ㅀ' => 'ᄚ',
'ㅁ' => 'ᄆ',
'ㅂ' => 'ᄇ',
'ㅃ' => 'ᄈ',
'ㅄ' => 'ᄡ',
'ㅅ' => 'ᄉ',
'ㅆ' => 'ᄊ',
'ㅇ' => 'ᄋ',
'ㅈ' => 'ᄌ',
'ㅉ' => 'ᄍ',
'ㅊ' => 'ᄎ',
'ㅋ' => 'ᄏ',
'ㅌ' => 'ᄐ',
'ㅍ' => 'ᄑ',
'ㅎ' => 'ᄒ',
'ㅏ' => 'ᅡ',
'ㅐ' => 'ᅢ',
'ㅑ' => 'ᅣ',
'ㅒ' => 'ᅤ',
'ㅓ' => 'ᅥ',
'ㅔ' => 'ᅦ',
'ㅕ' => 'ᅧ',
'ㅖ' => 'ᅨ',
'ㅗ' => 'ᅩ',
'ㅘ' => 'ᅪ',
'ㅙ' => 'ᅫ',
'ㅚ' => 'ᅬ',
'ㅛ' => 'ᅭ',
'ㅜ' => 'ᅮ',
'ㅝ' => 'ᅯ',
'ㅞ' => 'ᅰ',
'ㅟ' => 'ᅱ',
'ㅠ' => 'ᅲ',
'ㅡ' => 'ᅳ',
'ㅢ' => 'ᅴ',
'ㅣ' => 'ᅵ',
'' => '',
'ㅥ' => 'ᄔ',
'ㅦ' => 'ᄕ',
'ㅧ' => 'ᇇ',
'ㅨ' => 'ᇈ',
'ㅩ' => 'ᇌ',
'ㅪ' => 'ᇎ',
'ㅫ' => 'ᇓ',
'ㅬ' => 'ᇗ',
'ㅭ' => 'ᇙ',
'ㅮ' => 'ᄜ',
'ㅯ' => 'ᇝ',
'ㅰ' => 'ᇟ',
'ㅱ' => 'ᄝ',
'ㅲ' => 'ᄞ',
'ㅳ' => 'ᄠ',
'ㅴ' => 'ᄢ',
'ㅵ' => 'ᄣ',
'ㅶ' => 'ᄧ',
'ㅷ' => 'ᄩ',
'ㅸ' => 'ᄫ',
'ㅹ' => 'ᄬ',
'ㅺ' => 'ᄭ',
'ㅻ' => 'ᄮ',
'ㅼ' => 'ᄯ',
'ㅽ' => 'ᄲ',
'ㅾ' => 'ᄶ',
'ㅿ' => 'ᅀ',
'ㆀ' => 'ᅇ',
'ㆁ' => 'ᅌ',
'ㆂ' => 'ᇱ',
'ㆃ' => 'ᇲ',
'ㆄ' => 'ᅗ',
'ㆅ' => 'ᅘ',
'ㆆ' => 'ᅙ',
'ㆇ' => 'ᆄ',
'ㆈ' => 'ᆅ',
'ㆉ' => 'ᆈ',
'ㆊ' => 'ᆑ',
'ㆋ' => 'ᆒ',
'ㆌ' => 'ᆔ',
'ㆍ' => 'ᆞ',
'ㆎ' => 'ᆡ',
'㆒' => '一',
'㆓' => '二',
'㆔' => '三',
'㆕' => '四',
'㆖' => '上',
'㆗' => '中',
'㆘' => '下',
'㆙' => '甲',
'㆚' => '乙',
'㆛' => '丙',
'㆜' => '丁',
'㆝' => '天',
'㆞' => '地',
'㆟' => '人',
'㈀' => '(ᄀ)',
'㈁' => '(ᄂ)',
'㈂' => '(ᄃ)',
'㈃' => '(ᄅ)',
'㈄' => '(ᄆ)',
'㈅' => '(ᄇ)',
'㈆' => '(ᄉ)',
'㈇' => '(ᄋ)',
'㈈' => '(ᄌ)',
'㈉' => '(ᄎ)',
'㈊' => '(ᄏ)',
'㈋' => '(ᄐ)',
'㈌' => '(ᄑ)',
'㈍' => '(ᄒ)',
'㈎' => '(가)',
'㈏' => '(나)',
'㈐' => '(다)',
'㈑' => '(라)',
'㈒' => '(마)',
'㈓' => '(바)',
'㈔' => '(사)',
'㈕' => '(아)',
'㈖' => '(자)',
'㈗' => '(차)',
'㈘' => '(카)',
'㈙' => '(타)',
'㈚' => '(파)',
'㈛' => '(하)',
'㈜' => '(주)',
'㈝' => '(오전)',
'㈞' => '(오후)',
'㈠' => '(一)',
'㈡' => '(二)',
'㈢' => '(三)',
'㈣' => '(四)',
'㈤' => '(五)',
'㈥' => '(六)',
'㈦' => '(七)',
'㈧' => '(八)',
'㈨' => '(九)',
'㈩' => '(十)',
'㈪' => '(月)',
'㈫' => '(火)',
'㈬' => '(水)',
'㈭' => '(木)',
'㈮' => '(金)',
'㈯' => '(土)',
'㈰' => '(日)',
'㈱' => '(株)',
'㈲' => '(有)',
'㈳' => '(社)',
'㈴' => '(名)',
'㈵' => '(特)',
'㈶' => '(財)',
'㈷' => '(祝)',
'㈸' => '(労)',
'㈹' => '(代)',
'㈺' => '(呼)',
'㈻' => '(学)',
'㈼' => '(監)',
'㈽' => '(企)',
'㈾' => '(資)',
'㈿' => '(協)',
'㉀' => '(祭)',
'㉁' => '(休)',
'㉂' => '(自)',
'㉃' => '(至)',
'㉄' => '問',
'㉅' => '幼',
'㉆' => '文',
'㉇' => '箏',
'㉐' => 'PTE',
'㉑' => '21',
'㉒' => '22',
'㉓' => '23',
'㉔' => '24',
'㉕' => '25',
'㉖' => '26',
'㉗' => '27',
'㉘' => '28',
'㉙' => '29',
'㉚' => '30',
'㉛' => '31',
'㉜' => '32',
'㉝' => '33',
'㉞' => '34',
'㉟' => '35',
'㉠' => 'ᄀ',
'㉡' => 'ᄂ',
'㉢' => 'ᄃ',
'㉣' => 'ᄅ',
'㉤' => 'ᄆ',
'㉥' => 'ᄇ',
'㉦' => 'ᄉ',
'㉧' => 'ᄋ',
'㉨' => 'ᄌ',
'㉩' => 'ᄎ',
'㉪' => 'ᄏ',
'㉫' => 'ᄐ',
'㉬' => 'ᄑ',
'㉭' => 'ᄒ',
'㉮' => '가',
'㉯' => '나',
'㉰' => '다',
'㉱' => '라',
'㉲' => '마',
'㉳' => '바',
'㉴' => '사',
'㉵' => '아',
'㉶' => '자',
'㉷' => '차',
'㉸' => '카',
'㉹' => '타',
'㉺' => '파',
'㉻' => '하',
'㉼' => '참고',
'㉽' => '주의',
'㉾' => '우',
'㊀' => '一',
'㊁' => '二',
'㊂' => '三',
'㊃' => '四',
'㊄' => '五',
'㊅' => '六',
'㊆' => '七',
'㊇' => '八',
'㊈' => '九',
'㊉' => '十',
'㊊' => '月',
'㊋' => '火',
'㊌' => '水',
'㊍' => '木',
'㊎' => '金',
'㊏' => '土',
'㊐' => '日',
'㊑' => '株',
'㊒' => '有',
'㊓' => '社',
'㊔' => '名',
'㊕' => '特',
'㊖' => '財',
'㊗' => '祝',
'㊘' => '労',
'㊙' => '秘',
'㊚' => '男',
'㊛' => '女',
'㊜' => '適',
'㊝' => '優',
'㊞' => '印',
'㊟' => '注',
'㊠' => '項',
'㊡' => '休',
'㊢' => '写',
'㊣' => '正',
'㊤' => '上',
'㊥' => '中',
'㊦' => '下',
'㊧' => '左',
'㊨' => '右',
'㊩' => '医',
'㊪' => '宗',
'㊫' => '学',
'㊬' => '監',
'㊭' => '企',
'㊮' => '資',
'㊯' => '協',
'㊰' => '夜',
'㊱' => '36',
'㊲' => '37',
'㊳' => '38',
'㊴' => '39',
'㊵' => '40',
'㊶' => '41',
'㊷' => '42',
'㊸' => '43',
'㊹' => '44',
'㊺' => '45',
'㊻' => '46',
'㊼' => '47',
'㊽' => '48',
'㊾' => '49',
'㊿' => '50',
'㋀' => '1月',
'㋁' => '2月',
'㋂' => '3月',
'㋃' => '4月',
'㋄' => '5月',
'㋅' => '6月',
'㋆' => '7月',
'㋇' => '8月',
'㋈' => '9月',
'㋉' => '10月',
'㋊' => '11月',
'㋋' => '12月',
'㋌' => 'Hg',
'㋍' => 'erg',
'㋎' => 'eV',
'㋏' => 'LTD',
'㋐' => 'ア',
'㋑' => 'イ',
'㋒' => 'ウ',
'㋓' => 'エ',
'㋔' => 'オ',
'㋕' => 'カ',
'㋖' => 'キ',
'㋗' => 'ク',
'㋘' => 'ケ',
'㋙' => 'コ',
'㋚' => 'サ',
'㋛' => 'シ',
'㋜' => 'ス',
'㋝' => 'セ',
'㋞' => 'ソ',
'㋟' => 'タ',
'㋠' => 'チ',
'㋡' => 'ツ',
'㋢' => 'テ',
'㋣' => 'ト',
'㋤' => 'ナ',
'㋥' => 'ニ',
'㋦' => 'ヌ',
'㋧' => 'ネ',
'㋨' => '',
'㋩' => 'ハ',
'㋪' => 'ヒ',
'㋫' => 'フ',
'㋬' => 'ヘ',
'㋭' => 'ホ',
'㋮' => 'マ',
'㋯' => 'ミ',
'㋰' => 'ム',
'㋱' => 'メ',
'㋲' => 'モ',
'㋳' => 'ヤ',
'㋴' => 'ユ',
'㋵' => 'ヨ',
'㋶' => 'ラ',
'㋷' => 'リ',
'㋸' => 'ル',
'㋹' => 'レ',
'㋺' => 'ロ',
'㋻' => 'ワ',
'㋼' => 'ヰ',
'㋽' => 'ヱ',
'㋾' => 'ヲ',
'㋿' => '令和',
'㌀' => 'アパート',
'㌁' => 'アルファ',
'㌂' => 'アンペア',
'㌃' => 'アール',
'㌄' => 'イニング',
'㌅' => 'インチ',
'㌆' => 'ウォン',
'㌇' => 'エスクード',
'㌈' => 'エーカー',
'㌉' => 'オンス',
'㌊' => 'オーム',
'㌋' => 'カイリ',
'㌌' => 'カラット',
'㌍' => 'カロリー',
'㌎' => 'ガロン',
'㌏' => 'ガンマ',
'㌐' => 'ギガ',
'㌑' => 'ギニー',
'㌒' => 'キュリー',
'㌓' => 'ギルダー',
'㌔' => 'キロ',
'㌕' => 'キログラム',
'㌖' => 'キロメートル',
'㌗' => 'キロワット',
'㌘' => 'グラム',
'㌙' => 'グラムトン',
'㌚' => 'クルゼイロ',
'㌛' => 'クローネ',
'㌜' => 'ケース',
'㌝' => 'コルナ',
'㌞' => 'コーポ',
'㌟' => 'サイクル',
'㌠' => 'サンチーム',
'㌡' => 'シリング',
'㌢' => 'センチ',
'㌣' => 'セント',
'㌤' => 'ダース',
'㌥' => 'デシ',
'㌦' => 'ドル',
'㌧' => 'トン',
'㌨' => 'ナノ',
'㌩' => 'ノット',
'㌪' => 'ハイツ',
'㌫' => 'パーセント',
'㌬' => 'パーツ',
'㌭' => 'バーレル',
'㌮' => 'ピアストル',
'㌯' => 'ピクル',
'㌰' => 'ピコ',
'㌱' => 'ビル',
'㌲' => 'ファラッド',
'㌳' => 'フィート',
'㌴' => 'ブッシェル',
'㌵' => 'フラン',
'㌶' => 'ヘクタール',
'㌷' => 'ペソ',
'㌸' => 'ペニヒ',
'㌹' => 'ヘルツ',
'㌺' => 'ペンス',
'㌻' => 'ページ',
'㌼' => 'ベータ',
'㌽' => 'ポイント',
'㌾' => 'ボルト',
'㌿' => 'ホン',
'㍀' => 'ポンド',
'㍁' => 'ホール',
'㍂' => 'ホーン',
'㍃' => 'マイクロ',
'㍄' => 'マイル',
'㍅' => 'マッハ',
'㍆' => 'マルク',
'㍇' => 'マンション',
'㍈' => 'ミクロン',
'㍉' => 'ミリ',
'㍊' => 'ミリバール',
'㍋' => 'メガ',
'㍌' => 'メガトン',
'㍍' => 'メートル',
'㍎' => 'ヤード',
'㍏' => 'ヤール',
'㍐' => 'ユアン',
'㍑' => 'リットル',
'㍒' => 'リラ',
'㍓' => 'ルピー',
'㍔' => 'ルーブル',
'㍕' => 'レム',
'㍖' => 'レントゲン',
'㍗' => 'ワット',
'㍘' => '0点',
'㍙' => '1点',
'㍚' => '2点',
'㍛' => '3点',
'㍜' => '4点',
'㍝' => '5点',
'㍞' => '6点',
'㍟' => '7点',
'㍠' => '8点',
'㍡' => '9点',
'㍢' => '10点',
'㍣' => '11点',
'㍤' => '12点',
'㍥' => '13点',
'㍦' => '14点',
'㍧' => '15点',
'㍨' => '16点',
'㍩' => '17点',
'㍪' => '18点',
'㍫' => '19点',
'㍬' => '20点',
'㍭' => '21点',
'㍮' => '22点',
'㍯' => '23点',
'㍰' => '24点',
'㍱' => 'hPa',
'㍲' => 'da',
'㍳' => 'AU',
'㍴' => 'bar',
'㍵' => 'oV',
'㍶' => 'pc',
'㍷' => 'dm',
'㍸' => 'dm2',
'㍹' => 'dm3',
'㍺' => 'IU',
'㍻' => '平成',
'㍼' => '昭和',
'㍽' => '大正',
'㍾' => '明治',
'㍿' => '株式会社',
'㎀' => 'pA',
'㎁' => 'nA',
'㎂' => 'μA',
'㎃' => 'mA',
'㎄' => 'kA',
'㎅' => 'KB',
'㎆' => 'MB',
'㎇' => 'GB',
'㎈' => 'cal',
'㎉' => 'kcal',
'㎊' => 'pF',
'㎋' => 'nF',
'㎌' => 'μF',
'㎍' => 'μg',
'㎎' => 'mg',
'㎏' => 'kg',
'㎐' => 'Hz',
'㎑' => 'kHz',
'㎒' => 'MHz',
'㎓' => 'GHz',
'㎔' => 'THz',
'㎕' => 'μl',
'㎖' => 'ml',
'㎗' => 'dl',
'㎘' => 'kl',
'㎙' => 'fm',
'㎚' => 'nm',
'㎛' => 'μm',
'㎜' => 'mm',
'㎝' => 'cm',
'㎞' => 'km',
'㎟' => 'mm2',
'㎠' => 'cm2',
'㎡' => 'm2',
'㎢' => 'km2',
'㎣' => 'mm3',
'㎤' => 'cm3',
'㎥' => 'm3',
'㎦' => 'km3',
'㎧' => 'ms',
'㎨' => 'ms2',
'㎩' => 'Pa',
'㎪' => 'kPa',
'㎫' => 'MPa',
'㎬' => 'GPa',
'㎭' => 'rad',
'㎮' => 'rads',
'㎯' => 'rads2',
'㎰' => 'ps',
'㎱' => 'ns',
'㎲' => 'μs',
'㎳' => 'ms',
'㎴' => 'pV',
'㎵' => 'nV',
'㎶' => 'μV',
'㎷' => 'mV',
'㎸' => 'kV',
'㎹' => 'MV',
'㎺' => 'pW',
'㎻' => 'nW',
'㎼' => 'μW',
'㎽' => 'mW',
'㎾' => 'kW',
'㎿' => 'MW',
'㏀' => 'kΩ',
'㏁' => 'MΩ',
'㏂' => 'a.m.',
'㏃' => 'Bq',
'㏄' => 'cc',
'㏅' => 'cd',
'㏆' => 'Ckg',
'㏇' => 'Co.',
'㏈' => 'dB',
'㏉' => 'Gy',
'㏊' => 'ha',
'㏋' => 'HP',
'㏌' => 'in',
'㏍' => 'KK',
'㏎' => 'KM',
'㏏' => 'kt',
'㏐' => 'lm',
'㏑' => 'ln',
'㏒' => 'log',
'㏓' => 'lx',
'㏔' => 'mb',
'㏕' => 'mil',
'㏖' => 'mol',
'㏗' => 'PH',
'㏘' => 'p.m.',
'㏙' => 'PPM',
'㏚' => 'PR',
'㏛' => 'sr',
'㏜' => 'Sv',
'㏝' => 'Wb',
'㏞' => 'Vm',
'㏟' => 'Am',
'㏠' => '1日',
'㏡' => '2日',
'㏢' => '3日',
'㏣' => '4日',
'㏤' => '5日',
'㏥' => '6日',
'㏦' => '7日',
'㏧' => '8日',
'㏨' => '9日',
'㏩' => '10日',
'㏪' => '11日',
'㏫' => '12日',
'㏬' => '13日',
'㏭' => '14日',
'㏮' => '15日',
'㏯' => '16日',
'㏰' => '17日',
'㏱' => '18日',
'㏲' => '19日',
'㏳' => '20日',
'㏴' => '21日',
'㏵' => '22日',
'㏶' => '23日',
'㏷' => '24日',
'㏸' => '25日',
'㏹' => '26日',
'㏺' => '27日',
'㏻' => '28日',
'㏼' => '29日',
'㏽' => '30日',
'㏾' => '31日',
'㏿' => 'gal',
'ꚜ' => 'ъ',
'ꚝ' => 'ь',
'ꝰ' => 'ꝯ',
'ꟸ' => 'Ħ',
'ꟹ' => 'œ',
'ꭜ' => 'ꜧ',
'ꭝ' => 'ꬷ',
'ꭞ' => 'ɫ',
'ꭟ' => '',
'ꭩ' => 'ʍ',
'ff' => 'ff',
'fi' => 'fi',
'fl' => 'fl',
'ffi' => 'ffi',
'ffl' => 'ffl',
'ſt' => 'st',
'st' => 'st',
'ﬓ' => 'մն',
'ﬔ' => 'մե',
'ﬕ' => 'մի',
'ﬖ' => 'վն',
'ﬗ' => 'մխ',
'ﬠ' => 'ע',
'ﬡ' => 'א',
'ﬢ' => 'ד',
'ﬣ' => 'ה',
'ﬤ' => 'כ',
'ﬥ' => 'ל',
'ﬦ' => 'ם',
'ﬧ' => 'ר',
'ﬨ' => 'ת',
'﬩' => '+',
'ﭏ' => 'אל',
'ﭐ' => 'ٱ',
'ﭑ' => 'ٱ',
'ﭒ' => 'ٻ',
'ﭓ' => 'ٻ',
'ﭔ' => 'ٻ',
'ﭕ' => 'ٻ',
'ﭖ' => 'پ',
'ﭗ' => 'پ',
'ﭘ' => 'پ',
'ﭙ' => 'پ',
'ﭚ' => 'ڀ',
'ﭛ' => 'ڀ',
'ﭜ' => 'ڀ',
'ﭝ' => 'ڀ',
'ﭞ' => 'ٺ',
'ﭟ' => 'ٺ',
'ﭠ' => 'ٺ',
'ﭡ' => 'ٺ',
'ﭢ' => 'ٿ',
'ﭣ' => 'ٿ',
'ﭤ' => 'ٿ',
'ﭥ' => 'ٿ',
'ﭦ' => 'ٹ',
'ﭧ' => 'ٹ',
'ﭨ' => 'ٹ',
'ﭩ' => 'ٹ',
'ﭪ' => 'ڤ',
'ﭫ' => 'ڤ',
'ﭬ' => 'ڤ',
'ﭭ' => 'ڤ',
'ﭮ' => 'ڦ',
'ﭯ' => 'ڦ',
'ﭰ' => 'ڦ',
'ﭱ' => 'ڦ',
'ﭲ' => 'ڄ',
'ﭳ' => 'ڄ',
'ﭴ' => 'ڄ',
'ﭵ' => 'ڄ',
'ﭶ' => 'ڃ',
'ﭷ' => 'ڃ',
'ﭸ' => 'ڃ',
'ﭹ' => 'ڃ',
'ﭺ' => 'چ',
'ﭻ' => 'چ',
'ﭼ' => 'چ',
'ﭽ' => 'چ',
'ﭾ' => 'ڇ',
'ﭿ' => 'ڇ',
'ﮀ' => 'ڇ',
'ﮁ' => 'ڇ',
'ﮂ' => 'ڍ',
'ﮃ' => 'ڍ',
'ﮄ' => 'ڌ',
'ﮅ' => 'ڌ',
'ﮆ' => 'ڎ',
'ﮇ' => 'ڎ',
'ﮈ' => 'ڈ',
'ﮉ' => 'ڈ',
'ﮊ' => 'ژ',
'ﮋ' => 'ژ',
'ﮌ' => 'ڑ',
'ﮍ' => 'ڑ',
'ﮎ' => 'ک',
'ﮏ' => 'ک',
'ﮐ' => 'ک',
'ﮑ' => 'ک',
'ﮒ' => 'گ',
'ﮓ' => 'گ',
'ﮔ' => 'گ',
'ﮕ' => 'گ',
'ﮖ' => 'ڳ',
'ﮗ' => 'ڳ',
'ﮘ' => 'ڳ',
'ﮙ' => 'ڳ',
'ﮚ' => 'ڱ',
'ﮛ' => 'ڱ',
'ﮜ' => 'ڱ',
'ﮝ' => 'ڱ',
'ﮞ' => 'ں',
'ﮟ' => 'ں',
'ﮠ' => 'ڻ',
'ﮡ' => 'ڻ',
'ﮢ' => 'ڻ',
'ﮣ' => 'ڻ',
'ﮤ' => 'ۀ',
'ﮥ' => 'ۀ',
'' => 'ہ',
'' => 'ہ',
'' => 'ہ',
'' => 'ہ',
'' => 'ھ',
'' => 'ھ',
'' => 'ھ',
'' => 'ھ',
'ﮮ' => 'ے',
'ﮯ' => 'ے',
'ﮰ' => 'ۓ',
'ﮱ' => 'ۓ',
'ﯓ' => 'ڭ',
'ﯔ' => 'ڭ',
'ﯕ' => 'ڭ',
'ﯖ' => 'ڭ',
'ﯗ' => 'ۇ',
'ﯘ' => 'ۇ',
'ﯙ' => 'ۆ',
'ﯚ' => 'ۆ',
'ﯛ' => 'ۈ',
'ﯜ' => 'ۈ',
'ﯝ' => 'ۇٴ',
'ﯞ' => 'ۋ',
'ﯟ' => 'ۋ',
'ﯠ' => 'ۅ',
'ﯡ' => 'ۅ',
'ﯢ' => 'ۉ',
'ﯣ' => 'ۉ',
'ﯤ' => 'ې',
'ﯥ' => 'ې',
'ﯦ' => 'ې',
'ﯧ' => 'ې',
'ﯨ' => 'ى',
'ﯩ' => 'ى',
'ﯪ' => 'ئا',
'ﯫ' => 'ئا',
'ﯬ' => 'ئە',
'ﯭ' => 'ئە',
'ﯮ' => 'ئو',
'ﯯ' => 'ئو',
'ﯰ' => 'ئۇ',
'ﯱ' => 'ئۇ',
'ﯲ' => 'ئۆ',
'ﯳ' => 'ئۆ',
'ﯴ' => 'ئۈ',
'ﯵ' => 'ئۈ',
'ﯶ' => 'ئې',
'ﯷ' => 'ئې',
'ﯸ' => 'ئې',
'ﯹ' => 'ئى',
'ﯺ' => 'ئى',
'ﯻ' => 'ئى',
'ﯼ' => 'ی',
'ﯽ' => 'ی',
'ﯾ' => 'ی',
'ﯿ' => 'ی',
'ﰀ' => 'ئج',
'ﰁ' => 'ئح',
'ﰂ' => 'ئم',
'ﰃ' => 'ئى',
'ﰄ' => 'ئي',
'ﰅ' => 'بج',
'ﰆ' => 'بح',
'ﰇ' => 'بخ',
'ﰈ' => 'بم',
'ﰉ' => 'بى',
'ﰊ' => 'بي',
'ﰋ' => 'تج',
'ﰌ' => 'تح',
'ﰍ' => 'تخ',
'ﰎ' => 'تم',
'ﰏ' => 'تى',
'ﰐ' => 'تي',
'ﰑ' => 'ثج',
'ﰒ' => 'ثم',
'ﰓ' => 'ثى',
'ﰔ' => 'ثي',
'ﰕ' => 'جح',
'ﰖ' => 'جم',
'ﰗ' => 'حج',
'ﰘ' => 'حم',
'ﰙ' => 'خج',
'ﰚ' => 'خح',
'ﰛ' => 'خم',
'ﰜ' => 'سج',
'ﰝ' => 'سح',
'ﰞ' => 'سخ',
'ﰟ' => 'سم',
'ﰠ' => 'صح',
'ﰡ' => 'صم',
'ﰢ' => 'ضج',
'ﰣ' => 'ضح',
'ﰤ' => 'ضخ',
'ﰥ' => 'ضم',
'ﰦ' => 'طح',
'ﰧ' => 'طم',
'ﰨ' => 'ظم',
'ﰩ' => 'عج',
'ﰪ' => 'عم',
'ﰫ' => 'غج',
'ﰬ' => 'غم',
'ﰭ' => 'فج',
'ﰮ' => 'فح',
'ﰯ' => 'فخ',
'ﰰ' => 'فم',
'ﰱ' => 'فى',
'ﰲ' => 'في',
'ﰳ' => 'قح',
'ﰴ' => 'قم',
'ﰵ' => 'قى',
'ﰶ' => 'قي',
'ﰷ' => 'كا',
'ﰸ' => 'كج',
'ﰹ' => 'كح',
'ﰺ' => 'كخ',
'ﰻ' => 'كل',
'ﰼ' => 'كم',
'ﰽ' => 'كى',
'ﰾ' => 'كي',
'ﰿ' => 'لج',
'ﱀ' => 'لح',
'ﱁ' => 'لخ',
'ﱂ' => 'لم',
'ﱃ' => 'لى',
'ﱄ' => 'لي',
'ﱅ' => 'مج',
'ﱆ' => 'مح',
'ﱇ' => 'مخ',
'ﱈ' => 'مم',
'ﱉ' => 'مى',
'ﱊ' => 'مي',
'ﱋ' => 'نج',
'ﱌ' => 'نح',
'ﱍ' => 'نخ',
'ﱎ' => 'نم',
'ﱏ' => 'نى',
'ﱐ' => 'ني',
'ﱑ' => 'هج',
'ﱒ' => 'هم',
'ﱓ' => 'هى',
'ﱔ' => 'هي',
'ﱕ' => 'يج',
'ﱖ' => 'يح',
'ﱗ' => 'يخ',
'ﱘ' => 'يم',
'ﱙ' => 'يى',
'ﱚ' => 'يي',
'ﱛ' => 'ذٰ',
'ﱜ' => 'رٰ',
'ﱝ' => 'ىٰ',
'ﱞ' => ' ٌّ',
'ﱟ' => ' ٍّ',
'ﱠ' => ' َّ',
'ﱡ' => ' ُّ',
'ﱢ' => ' ِّ',
'ﱣ' => ' ّٰ',
'ﱤ' => 'ئر',
'ﱥ' => 'ئز',
'ﱦ' => 'ئم',
'ﱧ' => 'ئن',
'ﱨ' => 'ئى',
'ﱩ' => 'ئي',
'ﱪ' => 'بر',
'ﱫ' => 'بز',
'ﱬ' => 'بم',
'ﱭ' => 'بن',
'ﱮ' => 'بى',
'ﱯ' => 'بي',
'ﱰ' => 'تر',
'ﱱ' => 'تز',
'ﱲ' => 'تم',
'ﱳ' => 'تن',
'ﱴ' => 'تى',
'ﱵ' => 'تي',
'ﱶ' => 'ثر',
'ﱷ' => 'ثز',
'ﱸ' => 'ثم',
'ﱹ' => 'ثن',
'ﱺ' => 'ثى',
'ﱻ' => 'ثي',
'ﱼ' => 'فى',
'ﱽ' => 'في',
'ﱾ' => 'قى',
'ﱿ' => 'قي',
'ﲀ' => 'كا',
'ﲁ' => 'كل',
'ﲂ' => 'كم',
'ﲃ' => 'كى',
'ﲄ' => 'كي',
'ﲅ' => 'لم',
'ﲆ' => 'لى',
'ﲇ' => 'لي',
'ﲈ' => 'ما',
'ﲉ' => 'مم',
'ﲊ' => 'نر',
'ﲋ' => 'نز',
'ﲌ' => 'نم',
'ﲍ' => 'نن',
'ﲎ' => 'نى',
'ﲏ' => 'ني',
'ﲐ' => 'ىٰ',
'ﲑ' => 'ير',
'ﲒ' => 'يز',
'ﲓ' => 'يم',
'ﲔ' => 'ين',
'ﲕ' => 'يى',
'ﲖ' => 'يي',
'ﲗ' => 'ئج',
'ﲘ' => 'ئح',
'ﲙ' => 'ئخ',
'ﲚ' => 'ئم',
'ﲛ' => 'ئه',
'ﲜ' => 'بج',
'ﲝ' => 'بح',
'ﲞ' => 'بخ',
'ﲟ' => 'بم',
'ﲠ' => 'به',
'ﲡ' => 'تج',
'ﲢ' => 'تح',
'ﲣ' => 'تخ',
'ﲤ' => 'تم',
'ﲥ' => 'ته',
'ﲦ' => 'ثم',
'ﲧ' => 'جح',
'ﲨ' => 'جم',
'ﲩ' => 'حج',
'ﲪ' => 'حم',
'ﲫ' => 'خج',
'ﲬ' => 'خم',
'ﲭ' => 'سج',
'ﲮ' => 'سح',
'ﲯ' => 'سخ',
'ﲰ' => 'سم',
'ﲱ' => 'صح',
'ﲲ' => 'صخ',
'ﲳ' => 'صم',
'ﲴ' => 'ضج',
'ﲵ' => 'ضح',
'ﲶ' => 'ضخ',
'ﲷ' => 'ضم',
'ﲸ' => 'طح',
'ﲹ' => 'ظم',
'ﲺ' => 'عج',
'ﲻ' => 'عم',
'ﲼ' => 'غج',
'ﲽ' => 'غم',
'ﲾ' => 'فج',
'ﲿ' => 'فح',
'ﳀ' => 'فخ',
'ﳁ' => 'فم',
'ﳂ' => 'قح',
'ﳃ' => 'قم',
'ﳄ' => 'كج',
'ﳅ' => 'كح',
'ﳆ' => 'كخ',
'ﳇ' => 'كل',
'ﳈ' => 'كم',
'ﳉ' => 'لج',
'ﳊ' => 'لح',
'ﳋ' => 'لخ',
'ﳌ' => 'لم',
'ﳍ' => 'له',
'ﳎ' => 'مج',
'ﳏ' => 'مح',
'ﳐ' => 'مخ',
'ﳑ' => 'مم',
'ﳒ' => 'نج',
'ﳓ' => 'نح',
'ﳔ' => 'نخ',
'ﳕ' => 'نم',
'ﳖ' => 'نه',
'ﳗ' => 'هج',
'ﳘ' => 'هم',
'ﳙ' => 'هٰ',
'ﳚ' => 'يج',
'ﳛ' => 'يح',
'ﳜ' => 'يخ',
'ﳝ' => 'يم',
'ﳞ' => 'يه',
'ﳟ' => 'ئم',
'ﳠ' => 'ئه',
'ﳡ' => 'بم',
'ﳢ' => 'به',
'ﳣ' => 'تم',
'ﳤ' => 'ته',
'ﳥ' => 'ثم',
'ﳦ' => 'ثه',
'ﳧ' => 'سم',
'ﳨ' => 'سه',
'ﳩ' => 'شم',
'ﳪ' => 'شه',
'ﳫ' => 'كل',
'ﳬ' => 'كم',
'ﳭ' => 'لم',
'ﳮ' => 'نم',
'ﳯ' => 'نه',
'ﳰ' => 'يم',
'ﳱ' => 'يه',
'ﳲ' => 'ـَّ',
'ﳳ' => 'ـُّ',
'ﳴ' => 'ـِّ',
'ﳵ' => 'طى',
'ﳶ' => 'طي',
'ﳷ' => 'عى',
'ﳸ' => 'عي',
'ﳹ' => 'غى',
'ﳺ' => 'غي',
'ﳻ' => 'سى',
'ﳼ' => 'سي',
'ﳽ' => 'شى',
'ﳾ' => 'شي',
'ﳿ' => 'حى',
'ﴀ' => 'حي',
'ﴁ' => 'جى',
'ﴂ' => 'جي',
'ﴃ' => 'خى',
'ﴄ' => 'خي',
'ﴅ' => 'صى',
'ﴆ' => 'صي',
'ﴇ' => 'ضى',
'ﴈ' => 'ضي',
'ﴉ' => 'شج',
'ﴊ' => 'شح',
'ﴋ' => 'شخ',
'ﴌ' => 'شم',
'ﴍ' => 'شر',
'ﴎ' => 'سر',
'ﴏ' => 'صر',
'ﴐ' => 'ضر',
'ﴑ' => 'طى',
'ﴒ' => 'طي',
'ﴓ' => 'عى',
'ﴔ' => 'عي',
'ﴕ' => 'غى',
'ﴖ' => 'غي',
'ﴗ' => 'سى',
'ﴘ' => 'سي',
'ﴙ' => 'شى',
'ﴚ' => 'شي',
'ﴛ' => 'حى',
'ﴜ' => 'حي',
'ﴝ' => 'جى',
'ﴞ' => 'جي',
'ﴟ' => 'خى',
'ﴠ' => 'خي',
'ﴡ' => 'صى',
'ﴢ' => 'صي',
'ﴣ' => 'ضى',
'ﴤ' => 'ضي',
'ﴥ' => 'شج',
'ﴦ' => 'شح',
'ﴧ' => 'شخ',
'ﴨ' => 'شم',
'ﴩ' => 'شر',
'ﴪ' => 'سر',
'ﴫ' => 'صر',
'ﴬ' => 'ضر',
'ﴭ' => 'شج',
'ﴮ' => 'شح',
'ﴯ' => 'شخ',
'ﴰ' => 'شم',
'ﴱ' => 'سه',
'ﴲ' => 'شه',
'ﴳ' => 'طم',
'ﴴ' => 'سج',
'ﴵ' => 'سح',
'ﴶ' => 'سخ',
'ﴷ' => 'شج',
'ﴸ' => 'شح',
'ﴹ' => 'شخ',
'ﴺ' => 'طم',
'ﴻ' => 'ظم',
'ﴼ' => 'اً',
'ﴽ' => 'اً',
'ﵐ' => 'تجم',
'ﵑ' => 'تحج',
'ﵒ' => 'تحج',
'ﵓ' => 'تحم',
'ﵔ' => 'تخم',
'ﵕ' => 'تمج',
'ﵖ' => 'تمح',
'ﵗ' => 'تمخ',
'ﵘ' => 'جمح',
'ﵙ' => 'جمح',
'ﵚ' => 'حمي',
'ﵛ' => 'حمى',
'ﵜ' => 'سحج',
'ﵝ' => 'سجح',
'ﵞ' => 'سجى',
'ﵟ' => 'سمح',
'ﵠ' => 'سمح',
'ﵡ' => 'سمج',
'ﵢ' => 'سمم',
'ﵣ' => 'سمم',
'ﵤ' => 'صحح',
'ﵥ' => 'صحح',
'ﵦ' => 'صمم',
'ﵧ' => 'شحم',
'ﵨ' => 'شحم',
'ﵩ' => 'شجي',
'ﵪ' => 'شمخ',
'ﵫ' => 'شمخ',
'ﵬ' => 'شمم',
'ﵭ' => 'شمم',
'ﵮ' => 'ضحى',
'ﵯ' => 'ضخم',
'ﵰ' => 'ضخم',
'ﵱ' => 'طمح',
'ﵲ' => 'طمح',
'ﵳ' => 'طمم',
'ﵴ' => 'طمي',
'ﵵ' => 'عجم',
'ﵶ' => 'عمم',
'ﵷ' => 'عمم',
'ﵸ' => 'عمى',
'ﵹ' => 'غمم',
'ﵺ' => 'غمي',
'ﵻ' => 'غمى',
'ﵼ' => 'فخم',
'ﵽ' => 'فخم',
'ﵾ' => 'قمح',
'ﵿ' => 'قمم',
'ﶀ' => 'لحم',
'ﶁ' => 'لحي',
'ﶂ' => 'لحى',
'ﶃ' => 'لجج',
'ﶄ' => 'لجج',
'ﶅ' => 'لخم',
'ﶆ' => 'لخم',
'ﶇ' => 'لمح',
'ﶈ' => 'لمح',
'ﶉ' => 'محج',
'ﶊ' => 'محم',
'ﶋ' => 'محي',
'ﶌ' => 'مجح',
'ﶍ' => 'مجم',
'ﶎ' => 'مخج',
'ﶏ' => 'مخم',
'ﶒ' => 'مجخ',
'ﶓ' => 'همج',
'ﶔ' => 'همم',
'ﶕ' => 'نحم',
'ﶖ' => 'نحى',
'ﶗ' => 'نجم',
'ﶘ' => 'نجم',
'ﶙ' => 'نجى',
'ﶚ' => 'نمي',
'ﶛ' => 'نمى',
'ﶜ' => 'يمم',
'ﶝ' => 'يمم',
'ﶞ' => 'بخي',
'ﶟ' => 'تجي',
'ﶠ' => 'تجى',
'ﶡ' => 'تخي',
'ﶢ' => 'تخى',
'ﶣ' => 'تمي',
'ﶤ' => 'تمى',
'ﶥ' => 'جمي',
'ﶦ' => 'جحى',
'ﶧ' => 'جمى',
'ﶨ' => 'سخى',
'ﶩ' => 'صحي',
'ﶪ' => 'شحي',
'ﶫ' => 'ضحي',
'ﶬ' => 'لجي',
'ﶭ' => 'لمي',
'ﶮ' => 'يحي',
'ﶯ' => 'يجي',
'ﶰ' => 'يمي',
'ﶱ' => 'ممي',
'ﶲ' => 'قمي',
'ﶳ' => 'نحي',
'ﶴ' => 'قمح',
'ﶵ' => 'لحم',
'ﶶ' => 'عمي',
'ﶷ' => 'كمي',
'ﶸ' => 'نجح',
'ﶹ' => 'مخي',
'ﶺ' => 'لجم',
'ﶻ' => 'كمم',
'ﶼ' => 'لجم',
'ﶽ' => 'نجح',
'ﶾ' => 'جحي',
'ﶿ' => 'حجي',
'ﷀ' => 'مجي',
'ﷁ' => 'فمي',
'ﷂ' => 'بحي',
'ﷃ' => 'كمم',
'ﷄ' => 'عجم',
'ﷅ' => 'صمم',
'ﷆ' => 'سخي',
'ﷇ' => 'نجي',
'ﷰ' => 'صلے',
'ﷱ' => 'قلے',
'ﷲ' => 'الله',
'ﷳ' => 'اكبر',
'ﷴ' => 'محمد',
'ﷵ' => 'صلعم',
'ﷶ' => 'رسول',
'ﷷ' => 'عليه',
'ﷸ' => 'وسلم',
'ﷹ' => 'صلى',
'ﷺ' => 'صلى الله عليه وسلم',
'ﷻ' => 'جل جلاله',
'﷼' => 'ریال',
'︐' => ',',
'︑' => '、',
'︒' => '。',
'︓' => ':',
'︔' => ';',
'︕' => '!',
'︖' => '?',
'︗' => '〖',
'︘' => '〗',
'︙' => '...',
'' => '..',
'︱' => '—',
'︲' => '',
'︳' => '_',
'︴' => '_',
'︵' => '(',
'︶' => ')',
'︷' => '{',
'︸' => '}',
'︹' => '',
'︺' => '',
'︻' => '【',
'︼' => '】',
'︽' => '《',
'︾' => '》',
'︿' => '〈',
'﹀' => '〉',
'﹁' => '「',
'﹂' => '」',
'﹃' => '『',
'﹄' => '』',
'﹇' => '[',
'﹈' => ']',
'﹉' => ' ̅',
'﹊' => ' ̅',
'﹋' => ' ̅',
'﹌' => ' ̅',
'' => '_',
'' => '_',
'' => '_',
'﹐' => ',',
'﹑' => '、',
'﹒' => '.',
'﹔' => ';',
'﹕' => ':',
'﹖' => '?',
'﹗' => '!',
'' => '—',
'﹙' => '(',
'﹚' => ')',
'﹛' => '{',
'﹜' => '}',
'﹝' => '',
'﹞' => '',
'﹟' => '#',
'﹠' => '&',
'﹡' => '*',
'﹢' => '+',
'﹣' => '-',
'﹤' => '<',
'﹥' => '>',
'﹦' => '=',
'' => '\\',
'﹩' => '$',
'﹪' => '%',
'﹫' => '@',
'ﹰ' => ' ً',
'ﹱ' => 'ـً',
'ﹲ' => ' ٌ',
'ﹴ' => ' ٍ',
'ﹶ' => ' َ',
'ﹷ' => 'ـَ',
'ﹸ' => ' ُ',
'ﹹ' => 'ـُ',
'ﹺ' => ' ِ',
'ﹻ' => 'ـِ',
'ﹼ' => ' ّ',
'ﹽ' => 'ـّ',
'ﹾ' => ' ْ',
'ﹿ' => 'ـْ',
'ﺀ' => 'ء',
'ﺁ' => 'آ',
'ﺂ' => 'آ',
'ﺃ' => 'أ',
'ﺄ' => 'أ',
'ﺅ' => 'ؤ',
'ﺆ' => 'ؤ',
'ﺇ' => 'إ',
'ﺈ' => 'إ',
'ﺉ' => 'ئ',
'ﺊ' => 'ئ',
'ﺋ' => 'ئ',
'ﺌ' => 'ئ',
'' => 'ا',
'' => 'ا',
'ﺏ' => 'ب',
'ﺐ' => 'ب',
'ﺑ' => 'ب',
'ﺒ' => 'ب',
'ﺓ' => 'ة',
'ﺔ' => 'ة',
'ﺕ' => 'ت',
'ﺖ' => 'ت',
'ﺗ' => 'ت',
'ﺘ' => 'ت',
'ﺙ' => 'ث',
'ﺚ' => 'ث',
'ﺛ' => 'ث',
'ﺜ' => 'ث',
'ﺝ' => 'ج',
'ﺞ' => 'ج',
'ﺟ' => 'ج',
'ﺠ' => 'ج',
'ﺡ' => 'ح',
'ﺢ' => 'ح',
'ﺣ' => 'ح',
'ﺤ' => 'ح',
'ﺥ' => 'خ',
'ﺦ' => 'خ',
'ﺧ' => 'خ',
'ﺨ' => 'خ',
'ﺩ' => 'د',
'ﺪ' => 'د',
'ﺫ' => 'ذ',
'ﺬ' => 'ذ',
'ﺭ' => 'ر',
'ﺮ' => 'ر',
'ﺯ' => 'ز',
'ﺰ' => 'ز',
'ﺱ' => 'س',
'ﺲ' => 'س',
'ﺳ' => 'س',
'ﺴ' => 'س',
'ﺵ' => 'ش',
'ﺶ' => 'ش',
'ﺷ' => 'ش',
'ﺸ' => 'ش',
'ﺹ' => 'ص',
'ﺺ' => 'ص',
'ﺻ' => 'ص',
'ﺼ' => 'ص',
'ﺽ' => 'ض',
'ﺾ' => 'ض',
'ﺿ' => 'ض',
'ﻀ' => 'ض',
'ﻁ' => 'ط',
'ﻂ' => 'ط',
'ﻃ' => 'ط',
'ﻄ' => 'ط',
'ﻅ' => 'ظ',
'ﻆ' => 'ظ',
'ﻇ' => 'ظ',
'ﻈ' => 'ظ',
'ﻉ' => 'ع',
'ﻊ' => 'ع',
'ﻋ' => 'ع',
'ﻌ' => 'ع',
'ﻍ' => 'غ',
'ﻎ' => 'غ',
'ﻏ' => 'غ',
'ﻐ' => 'غ',
'ﻑ' => 'ف',
'ﻒ' => 'ف',
'ﻓ' => 'ف',
'ﻔ' => 'ف',
'ﻕ' => 'ق',
'ﻖ' => 'ق',
'ﻗ' => 'ق',
'ﻘ' => 'ق',
'ﻙ' => 'ك',
'ﻚ' => 'ك',
'ﻛ' => 'ك',
'ﻜ' => 'ك',
'ﻝ' => 'ل',
'ﻞ' => 'ل',
'ﻟ' => 'ل',
'ﻠ' => 'ل',
'ﻡ' => 'م',
'ﻢ' => 'م',
'ﻣ' => 'م',
'ﻤ' => 'م',
'ﻥ' => 'ن',
'ﻦ' => 'ن',
'ﻧ' => 'ن',
'ﻨ' => 'ن',
'' => 'ه',
'' => 'ه',
'' => 'ه',
'' => 'ه',
'ﻭ' => 'و',
'ﻮ' => 'و',
'ﻯ' => 'ى',
'ﻰ' => 'ى',
'ﻱ' => 'ي',
'ﻲ' => 'ي',
'ﻳ' => 'ي',
'ﻴ' => 'ي',
'ﻵ' => 'لآ',
'ﻶ' => 'لآ',
'ﻷ' => 'لأ',
'ﻸ' => 'لأ',
'ﻹ' => 'لإ',
'ﻺ' => 'لإ',
'ﻻ' => 'لا',
'ﻼ' => 'لا',
'' => '!',
'' => '"',
'' => '#',
'' => '$',
'' => '%',
'' => '&',
'' => '\'',
'' => '(',
'' => ')',
'' => '*',
'' => '+',
'' => ',',
'' => '-',
'' => '.',
'' => '/',
'' => '0',
'' => '1',
'' => '2',
'' => '3',
'' => '4',
'' => '5',
'' => '6',
'' => '7',
'' => '8',
'' => '9',
'' => ':',
'' => ';',
'' => '<',
'' => '=',
'' => '>',
'' => '?',
'' => '@',
'' => 'A',
'' => 'B',
'' => 'C',
'' => 'D',
'' => 'E',
'' => 'F',
'' => 'G',
'' => 'H',
'' => 'I',
'' => 'J',
'' => 'K',
'' => 'L',
'' => 'M',
'' => 'N',
'' => 'O',
'' => 'P',
'' => 'Q',
'' => 'R',
'' => 'S',
'' => 'T',
'' => 'U',
'' => 'V',
'' => 'W',
'' => 'X',
'' => 'Y',
'' => 'Z',
'' => '[',
'' => '\\',
'' => ']',
'' => '^',
'_' => '_',
'' => '`',
'' => 'a',
'' => 'b',
'' => 'c',
'' => 'd',
'' => 'e',
'' => 'f',
'' => 'g',
'' => 'h',
'' => 'i',
'' => 'j',
'' => 'k',
'' => 'l',
'' => 'm',
'' => 'n',
'' => 'o',
'' => 'p',
'' => 'q',
'' => 'r',
'' => 's',
'' => 't',
'' => 'u',
'' => 'v',
'' => 'w',
'' => 'x',
'' => 'y',
'' => 'z',
'' => '{',
'' => '|',
'' => '}',
'' => '~',
'⦅' => '⦅',
'⦆' => '⦆',
'。' => '。',
'「' => '「',
'」' => '」',
'、' => '、',
'・' => '・',
'ヲ' => 'ヲ',
'ァ' => 'ァ',
'ィ' => 'ィ',
'ゥ' => 'ゥ',
'ェ' => 'ェ',
'ォ' => 'ォ',
'ャ' => 'ャ',
'ュ' => 'ュ',
'ョ' => 'ョ',
'ッ' => 'ッ',
'ー' => 'ー',
'ア' => 'ア',
'イ' => 'イ',
'ウ' => 'ウ',
'エ' => 'エ',
'オ' => 'オ',
'カ' => 'カ',
'キ' => 'キ',
'ク' => 'ク',
'ケ' => 'ケ',
'コ' => 'コ',
'サ' => 'サ',
'シ' => 'シ',
'ス' => 'ス',
'セ' => 'セ',
'ソ' => 'ソ',
'タ' => 'タ',
'チ' => 'チ',
'ツ' => 'ツ',
'テ' => 'テ',
'ト' => 'ト',
'ナ' => 'ナ',
'ニ' => 'ニ',
'ヌ' => 'ヌ',
'ネ' => 'ネ',
'ノ' => '',
'ハ' => 'ハ',
'ヒ' => 'ヒ',
'フ' => 'フ',
'ヘ' => 'ヘ',
'ホ' => 'ホ',
'マ' => 'マ',
'ミ' => 'ミ',
'ム' => 'ム',
'メ' => 'メ',
'モ' => 'モ',
'ヤ' => 'ヤ',
'ユ' => 'ユ',
'ヨ' => 'ヨ',
'ラ' => 'ラ',
'リ' => 'リ',
'ル' => 'ル',
'レ' => 'レ',
'ロ' => 'ロ',
'ワ' => 'ワ',
'ン' => 'ン',
'゙' => '゙',
'゚' => '゚',
'' => '',
'ᄀ' => 'ᄀ',
'ᄁ' => 'ᄁ',
'ᆪ' => 'ᆪ',
'ᄂ' => 'ᄂ',
'ᆬ' => 'ᆬ',
'ᆭ' => 'ᆭ',
'ᄃ' => 'ᄃ',
'ᄄ' => 'ᄄ',
'ᄅ' => 'ᄅ',
'ᆰ' => 'ᆰ',
'ᆱ' => 'ᆱ',
'ᆲ' => 'ᆲ',
'ᆳ' => 'ᆳ',
'ᆴ' => 'ᆴ',
'ᆵ' => 'ᆵ',
'ᄚ' => 'ᄚ',
'ᄆ' => 'ᄆ',
'ᄇ' => 'ᄇ',
'ᄈ' => 'ᄈ',
'ᄡ' => 'ᄡ',
'ᄉ' => 'ᄉ',
'ᄊ' => 'ᄊ',
'ᄋ' => 'ᄋ',
'ᄌ' => 'ᄌ',
'ᄍ' => 'ᄍ',
'ᄎ' => 'ᄎ',
'ᄏ' => 'ᄏ',
'ᄐ' => 'ᄐ',
'ᄑ' => 'ᄑ',
'ᄒ' => 'ᄒ',
'ᅡ' => 'ᅡ',
'ᅢ' => 'ᅢ',
'ᅣ' => 'ᅣ',
'ᅤ' => 'ᅤ',
'ᅥ' => 'ᅥ',
'ᅦ' => 'ᅦ',
'ᅧ' => 'ᅧ',
'ᅨ' => 'ᅨ',
'ᅩ' => 'ᅩ',
'ᅪ' => 'ᅪ',
'ᅫ' => 'ᅫ',
'ᅬ' => 'ᅬ',
'ᅭ' => 'ᅭ',
'ᅮ' => 'ᅮ',
'ᅯ' => 'ᅯ',
'ᅰ' => 'ᅰ',
'ᅱ' => 'ᅱ',
'ᅲ' => 'ᅲ',
'ᅳ' => 'ᅳ',
'ᅴ' => 'ᅴ',
'ᅵ' => 'ᅵ',
'¢' => '¢',
'£' => '£',
'¬' => '¬',
' ̄' => ' ̄',
'¦' => '¦',
'¥' => '¥',
'₩' => '₩',
'' => '│',
'←' => '←',
'↑' => '↑',
'→' => '→',
'↓' => '↓',
'■' => '■',
'○' => '○',
'𝐀' => 'A',
'𝐁' => 'B',
'𝐂' => 'C',
'𝐃' => 'D',
'𝐄' => 'E',
'𝐅' => 'F',
'𝐆' => 'G',
'𝐇' => 'H',
'𝐈' => 'I',
'𝐉' => 'J',
'𝐊' => 'K',
'𝐋' => 'L',
'𝐌' => 'M',
'𝐍' => 'N',
'𝐎' => 'O',
'𝐏' => 'P',
'𝐐' => 'Q',
'𝐑' => 'R',
'𝐒' => 'S',
'𝐓' => 'T',
'𝐔' => 'U',
'𝐕' => 'V',
'𝐖' => 'W',
'𝐗' => 'X',
'𝐘' => 'Y',
'𝐙' => 'Z',
'𝐚' => 'a',
'𝐛' => 'b',
'𝐜' => 'c',
'𝐝' => 'd',
'𝐞' => 'e',
'𝐟' => 'f',
'𝐠' => 'g',
'𝐡' => 'h',
'𝐢' => 'i',
'𝐣' => 'j',
'𝐤' => 'k',
'𝐥' => 'l',
'𝐦' => 'm',
'𝐧' => 'n',
'𝐨' => 'o',
'𝐩' => 'p',
'𝐪' => 'q',
'𝐫' => 'r',
'𝐬' => 's',
'𝐭' => 't',
'𝐮' => 'u',
'𝐯' => 'v',
'𝐰' => 'w',
'𝐱' => 'x',
'𝐲' => 'y',
'𝐳' => 'z',
'𝐴' => 'A',
'𝐵' => 'B',
'𝐶' => 'C',
'𝐷' => 'D',
'𝐸' => 'E',
'𝐹' => 'F',
'𝐺' => 'G',
'𝐻' => 'H',
'𝐼' => 'I',
'𝐽' => 'J',
'𝐾' => 'K',
'𝐿' => 'L',
'𝑀' => 'M',
'𝑁' => 'N',
'𝑂' => 'O',
'𝑃' => 'P',
'𝑄' => 'Q',
'𝑅' => 'R',
'𝑆' => 'S',
'𝑇' => 'T',
'𝑈' => 'U',
'𝑉' => 'V',
'𝑊' => 'W',
'𝑋' => 'X',
'𝑌' => 'Y',
'𝑍' => 'Z',
'𝑎' => 'a',
'𝑏' => 'b',
'𝑐' => 'c',
'𝑑' => 'd',
'𝑒' => 'e',
'𝑓' => 'f',
'𝑔' => 'g',
'𝑖' => 'i',
'𝑗' => 'j',
'𝑘' => 'k',
'𝑙' => 'l',
'𝑚' => 'm',
'𝑛' => 'n',
'𝑜' => 'o',
'𝑝' => 'p',
'𝑞' => 'q',
'𝑟' => 'r',
'𝑠' => 's',
'𝑡' => 't',
'𝑢' => 'u',
'𝑣' => 'v',
'𝑤' => 'w',
'𝑥' => 'x',
'𝑦' => 'y',
'𝑧' => 'z',
'𝑨' => 'A',
'𝑩' => 'B',
'𝑪' => 'C',
'𝑫' => 'D',
'𝑬' => 'E',
'𝑭' => 'F',
'𝑮' => 'G',
'𝑯' => 'H',
'𝑰' => 'I',
'𝑱' => 'J',
'𝑲' => 'K',
'𝑳' => 'L',
'𝑴' => 'M',
'𝑵' => 'N',
'𝑶' => 'O',
'𝑷' => 'P',
'𝑸' => 'Q',
'𝑹' => 'R',
'𝑺' => 'S',
'𝑻' => 'T',
'𝑼' => 'U',
'𝑽' => 'V',
'𝑾' => 'W',
'𝑿' => 'X',
'𝒀' => 'Y',
'𝒁' => 'Z',
'𝒂' => 'a',
'𝒃' => 'b',
'𝒄' => 'c',
'𝒅' => 'd',
'𝒆' => 'e',
'𝒇' => 'f',
'𝒈' => 'g',
'𝒉' => 'h',
'𝒊' => 'i',
'𝒋' => 'j',
'𝒌' => 'k',
'𝒍' => 'l',
'𝒎' => 'm',
'𝒏' => 'n',
'𝒐' => 'o',
'𝒑' => 'p',
'𝒒' => 'q',
'𝒓' => 'r',
'𝒔' => 's',
'𝒕' => 't',
'𝒖' => 'u',
'𝒗' => 'v',
'𝒘' => 'w',
'𝒙' => 'x',
'𝒚' => 'y',
'𝒛' => 'z',
'𝒜' => 'A',
'𝒞' => 'C',
'𝒟' => 'D',
'𝒢' => 'G',
'𝒥' => 'J',
'𝒦' => 'K',
'𝒩' => 'N',
'𝒪' => 'O',
'𝒫' => 'P',
'𝒬' => 'Q',
'𝒮' => 'S',
'𝒯' => 'T',
'𝒰' => 'U',
'𝒱' => 'V',
'𝒲' => 'W',
'𝒳' => 'X',
'𝒴' => 'Y',
'𝒵' => 'Z',
'𝒶' => 'a',
'𝒷' => 'b',
'𝒸' => 'c',
'𝒹' => 'd',
'𝒻' => 'f',
'𝒽' => 'h',
'𝒾' => 'i',
'𝒿' => 'j',
'𝓀' => 'k',
'𝓁' => 'l',
'𝓂' => 'm',
'𝓃' => 'n',
'𝓅' => 'p',
'𝓆' => 'q',
'𝓇' => 'r',
'𝓈' => 's',
'𝓉' => 't',
'𝓊' => 'u',
'𝓋' => 'v',
'𝓌' => 'w',
'𝓍' => 'x',
'𝓎' => 'y',
'𝓏' => 'z',
'𝓐' => 'A',
'𝓑' => 'B',
'𝓒' => 'C',
'𝓓' => 'D',
'𝓔' => 'E',
'𝓕' => 'F',
'𝓖' => 'G',
'𝓗' => 'H',
'𝓘' => 'I',
'𝓙' => 'J',
'𝓚' => 'K',
'𝓛' => 'L',
'𝓜' => 'M',
'𝓝' => 'N',
'𝓞' => 'O',
'𝓟' => 'P',
'𝓠' => 'Q',
'𝓡' => 'R',
'𝓢' => 'S',
'𝓣' => 'T',
'𝓤' => 'U',
'𝓥' => 'V',
'𝓦' => 'W',
'𝓧' => 'X',
'𝓨' => 'Y',
'𝓩' => 'Z',
'𝓪' => 'a',
'𝓫' => 'b',
'𝓬' => 'c',
'𝓭' => 'd',
'𝓮' => 'e',
'𝓯' => 'f',
'𝓰' => 'g',
'𝓱' => 'h',
'𝓲' => 'i',
'𝓳' => 'j',
'𝓴' => 'k',
'𝓵' => 'l',
'𝓶' => 'm',
'𝓷' => 'n',
'𝓸' => 'o',
'𝓹' => 'p',
'𝓺' => 'q',
'𝓻' => 'r',
'𝓼' => 's',
'𝓽' => 't',
'𝓾' => 'u',
'𝓿' => 'v',
'𝔀' => 'w',
'𝔁' => 'x',
'𝔂' => 'y',
'𝔃' => 'z',
'𝔄' => 'A',
'𝔅' => 'B',
'𝔇' => 'D',
'𝔈' => 'E',
'𝔉' => 'F',
'𝔊' => 'G',
'𝔍' => 'J',
'𝔎' => 'K',
'𝔏' => 'L',
'𝔐' => 'M',
'𝔑' => 'N',
'𝔒' => 'O',
'𝔓' => 'P',
'𝔔' => 'Q',
'𝔖' => 'S',
'𝔗' => 'T',
'𝔘' => 'U',
'𝔙' => 'V',
'𝔚' => 'W',
'𝔛' => 'X',
'𝔜' => 'Y',
'𝔞' => 'a',
'𝔟' => 'b',
'𝔠' => 'c',
'𝔡' => 'd',
'𝔢' => 'e',
'𝔣' => 'f',
'𝔤' => 'g',
'𝔥' => 'h',
'𝔦' => 'i',
'𝔧' => 'j',
'𝔨' => 'k',
'𝔩' => 'l',
'𝔪' => 'm',
'𝔫' => 'n',
'𝔬' => 'o',
'𝔭' => 'p',
'𝔮' => 'q',
'𝔯' => 'r',
'𝔰' => 's',
'𝔱' => 't',
'𝔲' => 'u',
'𝔳' => 'v',
'𝔴' => 'w',
'𝔵' => 'x',
'𝔶' => 'y',
'𝔷' => 'z',
'𝔸' => 'A',
'𝔹' => 'B',
'𝔻' => 'D',
'𝔼' => 'E',
'𝔽' => 'F',
'𝔾' => 'G',
'𝕀' => 'I',
'𝕁' => 'J',
'𝕂' => 'K',
'𝕃' => 'L',
'𝕄' => 'M',
'𝕆' => 'O',
'𝕊' => 'S',
'𝕋' => 'T',
'𝕌' => 'U',
'𝕍' => 'V',
'𝕎' => 'W',
'𝕏' => 'X',
'𝕐' => 'Y',
'𝕒' => 'a',
'𝕓' => 'b',
'𝕔' => 'c',
'𝕕' => 'd',
'𝕖' => 'e',
'𝕗' => 'f',
'𝕘' => 'g',
'𝕙' => 'h',
'𝕚' => 'i',
'𝕛' => 'j',
'𝕜' => 'k',
'𝕝' => 'l',
'𝕞' => 'm',
'𝕟' => 'n',
'𝕠' => 'o',
'𝕡' => 'p',
'𝕢' => 'q',
'𝕣' => 'r',
'𝕤' => 's',
'𝕥' => 't',
'𝕦' => 'u',
'𝕧' => 'v',
'𝕨' => 'w',
'𝕩' => 'x',
'𝕪' => 'y',
'𝕫' => 'z',
'𝕬' => 'A',
'𝕭' => 'B',
'𝕮' => 'C',
'𝕯' => 'D',
'𝕰' => 'E',
'𝕱' => 'F',
'𝕲' => 'G',
'𝕳' => 'H',
'𝕴' => 'I',
'𝕵' => 'J',
'𝕶' => 'K',
'𝕷' => 'L',
'𝕸' => 'M',
'𝕹' => 'N',
'𝕺' => 'O',
'𝕻' => 'P',
'𝕼' => 'Q',
'𝕽' => 'R',
'𝕾' => 'S',
'𝕿' => 'T',
'𝖀' => 'U',
'𝖁' => 'V',
'𝖂' => 'W',
'𝖃' => 'X',
'𝖄' => 'Y',
'𝖅' => 'Z',
'𝖆' => 'a',
'𝖇' => 'b',
'𝖈' => 'c',
'𝖉' => 'd',
'𝖊' => 'e',
'𝖋' => 'f',
'𝖌' => 'g',
'𝖍' => 'h',
'𝖎' => 'i',
'𝖏' => 'j',
'𝖐' => 'k',
'𝖑' => 'l',
'𝖒' => 'm',
'𝖓' => 'n',
'𝖔' => 'o',
'𝖕' => 'p',
'𝖖' => 'q',
'𝖗' => 'r',
'𝖘' => 's',
'𝖙' => 't',
'𝖚' => 'u',
'𝖛' => 'v',
'𝖜' => 'w',
'𝖝' => 'x',
'𝖞' => 'y',
'𝖟' => 'z',
'𝖠' => 'A',
'𝖡' => 'B',
'𝖢' => 'C',
'𝖣' => 'D',
'𝖤' => 'E',
'𝖥' => 'F',
'𝖦' => 'G',
'𝖧' => 'H',
'𝖨' => 'I',
'𝖩' => 'J',
'𝖪' => 'K',
'𝖫' => 'L',
'𝖬' => 'M',
'𝖭' => 'N',
'𝖮' => 'O',
'𝖯' => 'P',
'𝖰' => 'Q',
'𝖱' => 'R',
'𝖲' => 'S',
'𝖳' => 'T',
'𝖴' => 'U',
'𝖵' => 'V',
'𝖶' => 'W',
'𝖷' => 'X',
'𝖸' => 'Y',
'𝖹' => 'Z',
'𝖺' => 'a',
'𝖻' => 'b',
'𝖼' => 'c',
'𝖽' => 'd',
'𝖾' => 'e',
'𝖿' => 'f',
'𝗀' => 'g',
'𝗁' => 'h',
'𝗂' => 'i',
'𝗃' => 'j',
'𝗄' => 'k',
'𝗅' => 'l',
'𝗆' => 'm',
'𝗇' => 'n',
'𝗈' => 'o',
'𝗉' => 'p',
'𝗊' => 'q',
'𝗋' => 'r',
'𝗌' => 's',
'𝗍' => 't',
'𝗎' => 'u',
'𝗏' => 'v',
'𝗐' => 'w',
'𝗑' => 'x',
'𝗒' => 'y',
'𝗓' => 'z',
'𝗔' => 'A',
'𝗕' => 'B',
'𝗖' => 'C',
'𝗗' => 'D',
'𝗘' => 'E',
'𝗙' => 'F',
'𝗚' => 'G',
'𝗛' => 'H',
'𝗜' => 'I',
'𝗝' => 'J',
'𝗞' => 'K',
'𝗟' => 'L',
'𝗠' => 'M',
'𝗡' => 'N',
'𝗢' => 'O',
'𝗣' => 'P',
'𝗤' => 'Q',
'𝗥' => 'R',
'𝗦' => 'S',
'𝗧' => 'T',
'𝗨' => 'U',
'𝗩' => 'V',
'𝗪' => 'W',
'𝗫' => 'X',
'𝗬' => 'Y',
'𝗭' => 'Z',
'𝗮' => 'a',
'𝗯' => 'b',
'𝗰' => 'c',
'𝗱' => 'd',
'𝗲' => 'e',
'𝗳' => 'f',
'𝗴' => 'g',
'𝗵' => 'h',
'𝗶' => 'i',
'𝗷' => 'j',
'𝗸' => 'k',
'𝗹' => 'l',
'𝗺' => 'm',
'𝗻' => 'n',
'𝗼' => 'o',
'𝗽' => 'p',
'𝗾' => 'q',
'𝗿' => 'r',
'𝘀' => 's',
'𝘁' => 't',
'𝘂' => 'u',
'𝘃' => 'v',
'𝘄' => 'w',
'𝘅' => 'x',
'𝘆' => 'y',
'𝘇' => 'z',
'𝘈' => 'A',
'𝘉' => 'B',
'𝘊' => 'C',
'𝘋' => 'D',
'𝘌' => 'E',
'𝘍' => 'F',
'𝘎' => 'G',
'𝘏' => 'H',
'𝘐' => 'I',
'𝘑' => 'J',
'𝘒' => 'K',
'𝘓' => 'L',
'𝘔' => 'M',
'𝘕' => 'N',
'𝘖' => 'O',
'𝘗' => 'P',
'𝘘' => 'Q',
'𝘙' => 'R',
'𝘚' => 'S',
'𝘛' => 'T',
'𝘜' => 'U',
'𝘝' => 'V',
'𝘞' => 'W',
'𝘟' => 'X',
'𝘠' => 'Y',
'𝘡' => 'Z',
'𝘢' => 'a',
'𝘣' => 'b',
'𝘤' => 'c',
'𝘥' => 'd',
'𝘦' => 'e',
'𝘧' => 'f',
'𝘨' => 'g',
'𝘩' => 'h',
'𝘪' => 'i',
'𝘫' => 'j',
'𝘬' => 'k',
'𝘭' => 'l',
'𝘮' => 'm',
'𝘯' => 'n',
'𝘰' => 'o',
'𝘱' => 'p',
'𝘲' => 'q',
'𝘳' => 'r',
'𝘴' => 's',
'𝘵' => 't',
'𝘶' => 'u',
'𝘷' => 'v',
'𝘸' => 'w',
'𝘹' => 'x',
'𝘺' => 'y',
'𝘻' => 'z',
'𝘼' => 'A',
'𝘽' => 'B',
'𝘾' => 'C',
'𝘿' => 'D',
'𝙀' => 'E',
'𝙁' => 'F',
'𝙂' => 'G',
'𝙃' => 'H',
'𝙄' => 'I',
'𝙅' => 'J',
'𝙆' => 'K',
'𝙇' => 'L',
'𝙈' => 'M',
'𝙉' => 'N',
'𝙊' => 'O',
'𝙋' => 'P',
'𝙌' => 'Q',
'𝙍' => 'R',
'𝙎' => 'S',
'𝙏' => 'T',
'𝙐' => 'U',
'𝙑' => 'V',
'𝙒' => 'W',
'𝙓' => 'X',
'𝙔' => 'Y',
'𝙕' => 'Z',
'𝙖' => 'a',
'𝙗' => 'b',
'𝙘' => 'c',
'𝙙' => 'd',
'𝙚' => 'e',
'𝙛' => 'f',
'𝙜' => 'g',
'𝙝' => 'h',
'𝙞' => 'i',
'𝙟' => 'j',
'𝙠' => 'k',
'𝙡' => 'l',
'𝙢' => 'm',
'𝙣' => 'n',
'𝙤' => 'o',
'𝙥' => 'p',
'𝙦' => 'q',
'𝙧' => 'r',
'𝙨' => 's',
'𝙩' => 't',
'𝙪' => 'u',
'𝙫' => 'v',
'𝙬' => 'w',
'𝙭' => 'x',
'𝙮' => 'y',
'𝙯' => 'z',
'𝙰' => 'A',
'𝙱' => 'B',
'𝙲' => 'C',
'𝙳' => 'D',
'𝙴' => 'E',
'𝙵' => 'F',
'𝙶' => 'G',
'𝙷' => 'H',
'𝙸' => 'I',
'𝙹' => 'J',
'𝙺' => 'K',
'𝙻' => 'L',
'𝙼' => 'M',
'𝙽' => 'N',
'𝙾' => 'O',
'𝙿' => 'P',
'𝚀' => 'Q',
'𝚁' => 'R',
'𝚂' => 'S',
'𝚃' => 'T',
'𝚄' => 'U',
'𝚅' => 'V',
'𝚆' => 'W',
'𝚇' => 'X',
'𝚈' => 'Y',
'𝚉' => 'Z',
'𝚊' => 'a',
'𝚋' => 'b',
'𝚌' => 'c',
'𝚍' => 'd',
'𝚎' => 'e',
'𝚏' => 'f',
'𝚐' => 'g',
'𝚑' => 'h',
'𝚒' => 'i',
'𝚓' => 'j',
'𝚔' => 'k',
'𝚕' => 'l',
'𝚖' => 'm',
'𝚗' => 'n',
'𝚘' => 'o',
'𝚙' => 'p',
'𝚚' => 'q',
'𝚛' => 'r',
'𝚜' => 's',
'𝚝' => 't',
'𝚞' => 'u',
'𝚟' => 'v',
'𝚠' => 'w',
'𝚡' => 'x',
'𝚢' => 'y',
'𝚣' => 'z',
'𝚤' => 'ı',
'𝚥' => 'ȷ',
'𝚨' => 'Α',
'𝚩' => 'Β',
'𝚪' => 'Γ',
'𝚫' => 'Δ',
'𝚬' => 'Ε',
'𝚭' => 'Ζ',
'𝚮' => 'Η',
'𝚯' => 'Θ',
'𝚰' => 'Ι',
'𝚱' => 'Κ',
'𝚲' => 'Λ',
'𝚳' => 'Μ',
'𝚴' => 'Ν',
'𝚵' => 'Ξ',
'𝚶' => 'Ο',
'𝚷' => 'Π',
'𝚸' => 'Ρ',
'𝚹' => 'Θ',
'𝚺' => 'Σ',
'𝚻' => 'Τ',
'𝚼' => 'Υ',
'𝚽' => 'Φ',
'𝚾' => 'Χ',
'𝚿' => 'Ψ',
'𝛀' => 'Ω',
'𝛁' => '∇',
'𝛂' => 'α',
'𝛃' => 'β',
'𝛄' => 'γ',
'𝛅' => 'δ',
'𝛆' => 'ε',
'𝛇' => 'ζ',
'𝛈' => 'η',
'𝛉' => 'θ',
'𝛊' => 'ι',
'𝛋' => 'κ',
'𝛌' => 'λ',
'𝛍' => 'μ',
'𝛎' => 'ν',
'𝛏' => 'ξ',
'𝛐' => 'ο',
'𝛑' => 'π',
'𝛒' => 'ρ',
'𝛓' => 'ς',
'𝛔' => 'σ',
'𝛕' => 'τ',
'𝛖' => 'υ',
'𝛗' => 'φ',
'𝛘' => 'χ',
'𝛙' => 'ψ',
'𝛚' => 'ω',
'𝛛' => '∂',
'𝛜' => 'ε',
'𝛝' => 'θ',
'𝛞' => 'κ',
'𝛟' => 'φ',
'𝛠' => 'ρ',
'𝛡' => 'π',
'𝛢' => 'Α',
'𝛣' => 'Β',
'𝛤' => 'Γ',
'𝛥' => 'Δ',
'𝛦' => 'Ε',
'𝛧' => 'Ζ',
'𝛨' => 'Η',
'𝛩' => 'Θ',
'𝛪' => 'Ι',
'𝛫' => 'Κ',
'𝛬' => 'Λ',
'𝛭' => 'Μ',
'𝛮' => 'Ν',
'𝛯' => 'Ξ',
'𝛰' => 'Ο',
'𝛱' => 'Π',
'𝛲' => 'Ρ',
'𝛳' => 'Θ',
'𝛴' => 'Σ',
'𝛵' => 'Τ',
'𝛶' => 'Υ',
'𝛷' => 'Φ',
'𝛸' => 'Χ',
'𝛹' => 'Ψ',
'𝛺' => 'Ω',
'𝛻' => '∇',
'𝛼' => 'α',
'𝛽' => 'β',
'𝛾' => 'γ',
'𝛿' => 'δ',
'𝜀' => 'ε',
'𝜁' => 'ζ',
'𝜂' => 'η',
'𝜃' => 'θ',
'𝜄' => 'ι',
'𝜅' => 'κ',
'𝜆' => 'λ',
'𝜇' => 'μ',
'𝜈' => 'ν',
'𝜉' => 'ξ',
'𝜊' => 'ο',
'𝜋' => 'π',
'𝜌' => 'ρ',
'𝜍' => 'ς',
'𝜎' => 'σ',
'𝜏' => 'τ',
'𝜐' => 'υ',
'𝜑' => 'φ',
'𝜒' => 'χ',
'𝜓' => 'ψ',
'𝜔' => 'ω',
'𝜕' => '∂',
'𝜖' => 'ε',
'𝜗' => 'θ',
'𝜘' => 'κ',
'𝜙' => 'φ',
'𝜚' => 'ρ',
'𝜛' => 'π',
'𝜜' => 'Α',
'𝜝' => 'Β',
'𝜞' => 'Γ',
'𝜟' => 'Δ',
'𝜠' => 'Ε',
'𝜡' => 'Ζ',
'𝜢' => 'Η',
'𝜣' => 'Θ',
'𝜤' => 'Ι',
'𝜥' => 'Κ',
'𝜦' => 'Λ',
'𝜧' => 'Μ',
'𝜨' => 'Ν',
'𝜩' => 'Ξ',
'𝜪' => 'Ο',
'𝜫' => 'Π',
'𝜬' => 'Ρ',
'𝜭' => 'Θ',
'𝜮' => 'Σ',
'𝜯' => 'Τ',
'𝜰' => 'Υ',
'𝜱' => 'Φ',
'𝜲' => 'Χ',
'𝜳' => 'Ψ',
'𝜴' => 'Ω',
'𝜵' => '∇',
'𝜶' => 'α',
'𝜷' => 'β',
'𝜸' => 'γ',
'𝜹' => 'δ',
'𝜺' => 'ε',
'𝜻' => 'ζ',
'𝜼' => 'η',
'𝜽' => 'θ',
'𝜾' => 'ι',
'𝜿' => 'κ',
'𝝀' => 'λ',
'𝝁' => 'μ',
'𝝂' => 'ν',
'𝝃' => 'ξ',
'𝝄' => 'ο',
'𝝅' => 'π',
'𝝆' => 'ρ',
'𝝇' => 'ς',
'𝝈' => 'σ',
'𝝉' => 'τ',
'𝝊' => 'υ',
'𝝋' => 'φ',
'𝝌' => 'χ',
'𝝍' => 'ψ',
'𝝎' => 'ω',
'𝝏' => '∂',
'𝝐' => 'ε',
'𝝑' => 'θ',
'𝝒' => 'κ',
'𝝓' => 'φ',
'𝝔' => 'ρ',
'𝝕' => 'π',
'𝝖' => 'Α',
'𝝗' => 'Β',
'𝝘' => 'Γ',
'𝝙' => 'Δ',
'𝝚' => 'Ε',
'𝝛' => 'Ζ',
'𝝜' => 'Η',
'𝝝' => 'Θ',
'𝝞' => 'Ι',
'𝝟' => 'Κ',
'𝝠' => 'Λ',
'𝝡' => 'Μ',
'𝝢' => 'Ν',
'𝝣' => 'Ξ',
'𝝤' => 'Ο',
'𝝥' => 'Π',
'𝝦' => 'Ρ',
'𝝧' => 'Θ',
'𝝨' => 'Σ',
'𝝩' => 'Τ',
'𝝪' => 'Υ',
'𝝫' => 'Φ',
'𝝬' => 'Χ',
'𝝭' => 'Ψ',
'𝝮' => 'Ω',
'𝝯' => '∇',
'𝝰' => 'α',
'𝝱' => 'β',
'𝝲' => 'γ',
'𝝳' => 'δ',
'𝝴' => 'ε',
'𝝵' => 'ζ',
'𝝶' => 'η',
'𝝷' => 'θ',
'𝝸' => 'ι',
'𝝹' => 'κ',
'𝝺' => 'λ',
'𝝻' => 'μ',
'𝝼' => 'ν',
'𝝽' => 'ξ',
'𝝾' => 'ο',
'𝝿' => 'π',
'𝞀' => 'ρ',
'𝞁' => 'ς',
'𝞂' => 'σ',
'𝞃' => 'τ',
'𝞄' => 'υ',
'𝞅' => 'φ',
'𝞆' => 'χ',
'𝞇' => 'ψ',
'𝞈' => 'ω',
'𝞉' => '∂',
'𝞊' => 'ε',
'𝞋' => 'θ',
'𝞌' => 'κ',
'𝞍' => 'φ',
'𝞎' => 'ρ',
'𝞏' => 'π',
'𝞐' => 'Α',
'𝞑' => 'Β',
'𝞒' => 'Γ',
'𝞓' => 'Δ',
'𝞔' => 'Ε',
'𝞕' => 'Ζ',
'𝞖' => 'Η',
'𝞗' => 'Θ',
'𝞘' => 'Ι',
'𝞙' => 'Κ',
'𝞚' => 'Λ',
'𝞛' => 'Μ',
'𝞜' => 'Ν',
'𝞝' => 'Ξ',
'𝞞' => 'Ο',
'𝞟' => 'Π',
'𝞠' => 'Ρ',
'𝞡' => 'Θ',
'𝞢' => 'Σ',
'𝞣' => 'Τ',
'𝞤' => 'Υ',
'𝞥' => 'Φ',
'𝞦' => 'Χ',
'𝞧' => 'Ψ',
'𝞨' => 'Ω',
'𝞩' => '∇',
'𝞪' => 'α',
'𝞫' => 'β',
'𝞬' => 'γ',
'𝞭' => 'δ',
'𝞮' => 'ε',
'𝞯' => 'ζ',
'𝞰' => 'η',
'𝞱' => 'θ',
'𝞲' => 'ι',
'𝞳' => 'κ',
'𝞴' => 'λ',
'𝞵' => 'μ',
'𝞶' => 'ν',
'𝞷' => 'ξ',
'𝞸' => 'ο',
'𝞹' => 'π',
'𝞺' => 'ρ',
'𝞻' => 'ς',
'𝞼' => 'σ',
'𝞽' => 'τ',
'𝞾' => 'υ',
'𝞿' => 'φ',
'𝟀' => 'χ',
'𝟁' => 'ψ',
'𝟂' => 'ω',
'𝟃' => '∂',
'𝟄' => 'ε',
'𝟅' => 'θ',
'𝟆' => 'κ',
'𝟇' => 'φ',
'𝟈' => 'ρ',
'𝟉' => 'π',
'𝟊' => 'Ϝ',
'𝟋' => 'ϝ',
'𝟎' => '0',
'𝟏' => '1',
'𝟐' => '2',
'𝟑' => '3',
'𝟒' => '4',
'𝟓' => '5',
'𝟔' => '6',
'𝟕' => '7',
'𝟖' => '8',
'𝟗' => '9',
'𝟘' => '0',
'𝟙' => '1',
'𝟚' => '2',
'𝟛' => '3',
'𝟜' => '4',
'𝟝' => '5',
'𝟞' => '6',
'𝟟' => '7',
'𝟠' => '8',
'𝟡' => '9',
'𝟢' => '0',
'𝟣' => '1',
'𝟤' => '2',
'𝟥' => '3',
'𝟦' => '4',
'𝟧' => '5',
'𝟨' => '6',
'𝟩' => '7',
'𝟪' => '8',
'𝟫' => '9',
'𝟬' => '0',
'𝟭' => '1',
'𝟮' => '2',
'𝟯' => '3',
'𝟰' => '4',
'𝟱' => '5',
'𝟲' => '6',
'𝟳' => '7',
'𝟴' => '8',
'𝟵' => '9',
'𝟶' => '0',
'𝟷' => '1',
'𝟸' => '2',
'𝟹' => '3',
'𝟺' => '4',
'𝟻' => '5',
'𝟼' => '6',
'𝟽' => '7',
'𝟾' => '8',
'𝟿' => '9',
'𞸀' => 'ا',
'𞸁' => 'ب',
'𞸂' => 'ج',
'𞸃' => 'د',
'𞸅' => 'و',
'𞸆' => 'ز',
'𞸇' => 'ح',
'𞸈' => 'ط',
'𞸉' => 'ي',
'𞸊' => 'ك',
'𞸋' => 'ل',
'𞸌' => 'م',
'𞸍' => 'ن',
'𞸎' => 'س',
'𞸏' => 'ع',
'𞸐' => 'ف',
'𞸑' => 'ص',
'𞸒' => 'ق',
'𞸓' => 'ر',
'𞸔' => 'ش',
'𞸕' => 'ت',
'𞸖' => 'ث',
'𞸗' => 'خ',
'𞸘' => 'ذ',
'𞸙' => 'ض',
'𞸚' => 'ظ',
'𞸛' => 'غ',
'𞸜' => 'ٮ',
'𞸝' => 'ں',
'𞸞' => 'ڡ',
'𞸟' => 'ٯ',
'𞸡' => 'ب',
'𞸢' => 'ج',
'𞸤' => 'ه',
'𞸧' => 'ح',
'𞸩' => 'ي',
'𞸪' => 'ك',
'𞸫' => 'ل',
'𞸬' => 'م',
'𞸭' => 'ن',
'𞸮' => 'س',
'𞸯' => 'ع',
'𞸰' => 'ف',
'𞸱' => 'ص',
'𞸲' => 'ق',
'𞸴' => 'ش',
'𞸵' => 'ت',
'𞸶' => 'ث',
'𞸷' => 'خ',
'𞸹' => 'ض',
'𞸻' => 'غ',
'𞹂' => 'ج',
'𞹇' => 'ح',
'𞹉' => 'ي',
'𞹋' => 'ل',
'𞹍' => 'ن',
'𞹎' => 'س',
'𞹏' => 'ع',
'𞹑' => 'ص',
'𞹒' => 'ق',
'𞹔' => 'ش',
'𞹗' => 'خ',
'𞹙' => 'ض',
'𞹛' => 'غ',
'𞹝' => 'ں',
'𞹟' => 'ٯ',
'𞹡' => 'ب',
'𞹢' => 'ج',
'𞹤' => 'ه',
'𞹧' => 'ح',
'𞹨' => 'ط',
'𞹩' => 'ي',
'𞹪' => 'ك',
'𞹬' => 'م',
'𞹭' => 'ن',
'𞹮' => 'س',
'𞹯' => 'ع',
'𞹰' => 'ف',
'𞹱' => 'ص',
'𞹲' => 'ق',
'𞹴' => 'ش',
'𞹵' => 'ت',
'𞹶' => 'ث',
'𞹷' => 'خ',
'𞹹' => 'ض',
'𞹺' => 'ظ',
'𞹻' => 'غ',
'𞹼' => 'ٮ',
'𞹾' => 'ڡ',
'𞺀' => 'ا',
'𞺁' => 'ب',
'𞺂' => 'ج',
'𞺃' => 'د',
'𞺄' => 'ه',
'𞺅' => 'و',
'𞺆' => 'ز',
'𞺇' => 'ح',
'𞺈' => 'ط',
'𞺉' => 'ي',
'𞺋' => 'ل',
'𞺌' => 'م',
'𞺍' => 'ن',
'𞺎' => 'س',
'𞺏' => 'ع',
'𞺐' => 'ف',
'𞺑' => 'ص',
'𞺒' => 'ق',
'𞺓' => 'ر',
'𞺔' => 'ش',
'𞺕' => 'ت',
'𞺖' => 'ث',
'𞺗' => 'خ',
'𞺘' => 'ذ',
'𞺙' => 'ض',
'𞺚' => 'ظ',
'𞺛' => 'غ',
'𞺡' => 'ب',
'𞺢' => 'ج',
'𞺣' => 'د',
'𞺥' => 'و',
'𞺦' => 'ز',
'𞺧' => 'ح',
'𞺨' => 'ط',
'𞺩' => 'ي',
'𞺫' => 'ل',
'𞺬' => 'م',
'𞺭' => 'ن',
'𞺮' => 'س',
'𞺯' => 'ع',
'𞺰' => 'ف',
'𞺱' => 'ص',
'𞺲' => 'ق',
'𞺳' => 'ر',
'𞺴' => 'ش',
'𞺵' => 'ت',
'𞺶' => 'ث',
'𞺷' => 'خ',
'𞺸' => 'ذ',
'𞺹' => 'ض',
'𞺺' => 'ظ',
'𞺻' => 'غ',
'🄀' => '0.',
'🄁' => '0,',
'🄂' => '1,',
'🄃' => '2,',
'🄄' => '3,',
'🄅' => '4,',
'🄆' => '5,',
'🄇' => '6,',
'🄈' => '7,',
'🄉' => '8,',
'🄊' => '9,',
'🄐' => '(A)',
'🄑' => '(B)',
'🄒' => '(C)',
'🄓' => '(D)',
'🄔' => '(E)',
'🄕' => '(F)',
'🄖' => '(G)',
'🄗' => '(H)',
'🄘' => '(I)',
'🄙' => '(J)',
'🄚' => '(K)',
'🄛' => '(L)',
'🄜' => '(M)',
'🄝' => '(N)',
'🄞' => '(O)',
'🄟' => '(P)',
'🄠' => '(Q)',
'🄡' => '(R)',
'🄢' => '(S)',
'🄣' => '(T)',
'🄤' => '(U)',
'🄥' => '(V)',
'🄦' => '(W)',
'🄧' => '(X)',
'🄨' => '(Y)',
'🄩' => '(Z)',
'🄪' => 'S',
'🄫' => 'C',
'🄬' => 'R',
'🄭' => 'CD',
'🄮' => 'WZ',
'🄰' => 'A',
'🄱' => 'B',
'🄲' => 'C',
'🄳' => 'D',
'🄴' => 'E',
'🄵' => 'F',
'🄶' => 'G',
'🄷' => 'H',
'🄸' => 'I',
'🄹' => 'J',
'🄺' => 'K',
'🄻' => 'L',
'🄼' => 'M',
'🄽' => 'N',
'🄾' => 'O',
'🄿' => 'P',
'🅀' => 'Q',
'🅁' => 'R',
'🅂' => 'S',
'🅃' => 'T',
'🅄' => 'U',
'🅅' => 'V',
'🅆' => 'W',
'🅇' => 'X',
'🅈' => 'Y',
'🅉' => 'Z',
'🅊' => 'HV',
'🅋' => 'MV',
'🅌' => 'SD',
'🅍' => 'SS',
'🅎' => 'PPV',
'🅏' => 'WC',
'🅪' => 'MC',
'🅫' => 'MD',
'🅬' => 'MR',
'🆐' => 'DJ',
'🈀' => 'ほか',
'🈁' => 'ココ',
'🈂' => 'サ',
'🈐' => '手',
'🈑' => '字',
'🈒' => '双',
'🈓' => 'デ',
'🈔' => '二',
'🈕' => '多',
'🈖' => '解',
'🈗' => '天',
'🈘' => '交',
'🈙' => '映',
'🈚' => '無',
'🈛' => '料',
'🈜' => '前',
'🈝' => '後',
'🈞' => '再',
'🈟' => '新',
'🈠' => '初',
'🈡' => '終',
'🈢' => '生',
'🈣' => '販',
'🈤' => '声',
'🈥' => '吹',
'🈦' => '演',
'🈧' => '投',
'🈨' => '捕',
'🈩' => '一',
'🈪' => '三',
'🈫' => '遊',
'🈬' => '左',
'🈭' => '中',
'🈮' => '右',
'🈯' => '指',
'🈰' => '走',
'🈱' => '打',
'🈲' => '禁',
'🈳' => '空',
'🈴' => '合',
'🈵' => '満',
'🈶' => '有',
'🈷' => '月',
'🈸' => '申',
'🈹' => '割',
'🈺' => '営',
'🈻' => '配',
'🉀' => '〔本〕',
'🉁' => '〔三〕',
'🉂' => '〔二〕',
'🉃' => '〔安〕',
'🉄' => '〔点〕',
'🉅' => '〔打〕',
'🉆' => '〔盗〕',
'🉇' => '〔勝〕',
'🉈' => '〔敗〕',
'🉐' => '得',
'🉑' => '可',
'🯰' => '0',
'🯱' => '1',
'🯲' => '2',
'🯳' => '3',
'🯴' => '4',
'🯵' => '5',
'🯶' => '6',
'🯷' => '7',
'🯸' => '8',
'🯹' => '9',
);
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Symfony\Polyfill\Mbstring as p;
if (!function_exists('mb_convert_encoding')) {
function mb_convert_encoding($string, $to_encoding, $from_encoding = null) { return p\Mbstring::mb_convert_encoding($string, $to_encoding, $from_encoding); }
}
if (!function_exists('mb_decode_mimeheader')) {
function mb_decode_mimeheader($string) { return p\Mbstring::mb_decode_mimeheader($string); }
}
if (!function_exists('mb_encode_mimeheader')) {
function mb_encode_mimeheader($string, $charset = null, $transfer_encoding = null, $newline = null, $indent = null) { return p\Mbstring::mb_encode_mimeheader($string, $charset, $transfer_encoding, $newline, $indent); }
}
if (!function_exists('mb_decode_numericentity')) {
function mb_decode_numericentity($string, $map, $encoding = null) { return p\Mbstring::mb_decode_numericentity($string, $map, $encoding); }
}
if (!function_exists('mb_encode_numericentity')) {
function mb_encode_numericentity($string, $map, $encoding = null, $hex = false) { return p\Mbstring::mb_encode_numericentity($string, $map, $encoding, $hex); }
}
if (!function_exists('mb_convert_case')) {
function mb_convert_case($string, $mode, $encoding = null) { return p\Mbstring::mb_convert_case($string, $mode, $encoding); }
}
if (!function_exists('mb_internal_encoding')) {
function mb_internal_encoding($encoding = null) { return p\Mbstring::mb_internal_encoding($encoding); }
}
if (!function_exists('mb_language')) {
function mb_language($language = null) { return p\Mbstring::mb_language($language); }
}
if (!function_exists('mb_list_encodings')) {
function mb_list_encodings() { return p\Mbstring::mb_list_encodings(); }
}
if (!function_exists('mb_encoding_aliases')) {
function mb_encoding_aliases($encoding) { return p\Mbstring::mb_encoding_aliases($encoding); }
}
if (!function_exists('mb_check_encoding')) {
function mb_check_encoding($value = null, $encoding = null) { return p\Mbstring::mb_check_encoding($value, $encoding); }
}
if (!function_exists('mb_detect_encoding')) {
function mb_detect_encoding($string, $encodings = null, $strict = false) { return p\Mbstring::mb_detect_encoding($string, $encodings, $strict); }
}
if (!function_exists('mb_detect_order')) {
function mb_detect_order($encoding = null) { return p\Mbstring::mb_detect_order($encoding); }
}
if (!function_exists('mb_parse_str')) {
function mb_parse_str($string, &$result = array()) { parse_str($string, $result); }
}
if (!function_exists('mb_strlen')) {
function mb_strlen($string, $encoding = null) { return p\Mbstring::mb_strlen($string, $encoding); }
}
if (!function_exists('mb_strpos')) {
function mb_strpos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_strpos($haystack, $needle, $offset, $encoding); }
}
if (!function_exists('mb_strtolower')) {
function mb_strtolower($string, $encoding = null) { return p\Mbstring::mb_strtolower($string, $encoding); }
}
if (!function_exists('mb_strtoupper')) {
function mb_strtoupper($string, $encoding = null) { return p\Mbstring::mb_strtoupper($string, $encoding); }
}
if (!function_exists('mb_substitute_character')) {
function mb_substitute_character($substitute_character = null) { return p\Mbstring::mb_substitute_character($substitute_character); }
}
if (!function_exists('mb_substr')) {
function mb_substr($string, $start, $length = 2147483647, $encoding = null) { return p\Mbstring::mb_substr($string, $start, $length, $encoding); }
}
if (!function_exists('mb_stripos')) {
function mb_stripos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_stripos($haystack, $needle, $offset, $encoding); }
}
if (!function_exists('mb_stristr')) {
function mb_stristr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_stristr($haystack, $needle, $before_needle, $encoding); }
}
if (!function_exists('mb_strrchr')) {
function mb_strrchr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_strrchr($haystack, $needle, $before_needle, $encoding); }
}
if (!function_exists('mb_strrichr')) {
function mb_strrichr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_strrichr($haystack, $needle, $before_needle, $encoding); }
}
if (!function_exists('mb_strripos')) {
function mb_strripos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_strripos($haystack, $needle, $offset, $encoding); }
}
if (!function_exists('mb_strrpos')) {
function mb_strrpos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_strrpos($haystack, $needle, $offset, $encoding); }
}
if (!function_exists('mb_strstr')) {
function mb_strstr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_strstr($haystack, $needle, $before_needle, $encoding); }
}
if (!function_exists('mb_get_info')) {
function mb_get_info($type = 'all') { return p\Mbstring::mb_get_info($type); }
}
if (!function_exists('mb_http_output')) {
function mb_http_output($encoding = null) { return p\Mbstring::mb_http_output($encoding); }
}
if (!function_exists('mb_strwidth')) {
function mb_strwidth($string, $encoding = null) { return p\Mbstring::mb_strwidth($string, $encoding); }
}
if (!function_exists('mb_substr_count')) {
function mb_substr_count($haystack, $needle, $encoding = null) { return p\Mbstring::mb_substr_count($haystack, $needle, $encoding); }
}
if (!function_exists('mb_output_handler')) {
function mb_output_handler($string, $status) { return p\Mbstring::mb_output_handler($string, $status); }
}
if (!function_exists('mb_http_input')) {
function mb_http_input($type = '') { return p\Mbstring::mb_http_input($type); }
}
if (!function_exists('mb_convert_variables')) {
if (PHP_VERSION_ID >= 80000) {
function mb_convert_variables($to_encoding, $from_encoding, &$var, &...$vars) { return p\Mbstring::mb_convert_variables($to_encoding, $from_encoding, $var, ...$vars); }
} else {
function mb_convert_variables($to_encoding, $from_encoding, &...$vars) { return p\Mbstring::mb_convert_variables($to_encoding, $from_encoding, ...$vars); }
}
}
if (!function_exists('mb_ord')) {
function mb_ord($string, $encoding = null) { return p\Mbstring::mb_ord($string, $encoding); }
}
if (!function_exists('mb_chr')) {
function mb_chr($codepoint, $encoding = null) { return p\Mbstring::mb_chr($codepoint, $encoding); }
}
if (!function_exists('mb_scrub')) {
function mb_scrub($string, $encoding = null) { $encoding = null === $encoding ? mb_internal_encoding() : $encoding; return mb_convert_encoding($string, $encoding, $encoding); }
}
if (!function_exists('mb_str_split')) {
function mb_str_split($string, $length = 1, $encoding = null) { return p\Mbstring::mb_str_split($string, $length, $encoding); }
}
if (extension_loaded('mbstring')) {
return;
}
if (!defined('MB_CASE_UPPER')) {
define('MB_CASE_UPPER', 0);
}
if (!defined('MB_CASE_LOWER')) {
define('MB_CASE_LOWER', 1);
}
if (!defined('MB_CASE_TITLE')) {
define('MB_CASE_TITLE', 2);
}
Copyright (c) 2015-2019 Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Polyfill\Mbstring;
/**
* Partial mbstring implementation in PHP, iconv based, UTF-8 centric.
*
* Implemented:
* - mb_chr - Returns a specific character from its Unicode code point
* - mb_convert_encoding - Convert character encoding
* - mb_convert_variables - Convert character code in variable(s)
* - mb_decode_mimeheader - Decode string in MIME header field
* - mb_encode_mimeheader - Encode string for MIME header XXX NATIVE IMPLEMENTATION IS REALLY BUGGED
* - mb_decode_numericentity - Decode HTML numeric string reference to character
* - mb_encode_numericentity - Encode character to HTML numeric string reference
* - mb_convert_case - Perform case folding on a string
* - mb_detect_encoding - Detect character encoding
* - mb_get_info - Get internal settings of mbstring
* - mb_http_input - Detect HTTP input character encoding
* - mb_http_output - Set/Get HTTP output character encoding
* - mb_internal_encoding - Set/Get internal character encoding
* - mb_list_encodings - Returns an array of all supported encodings
* - mb_ord - Returns the Unicode code point of a character
* - mb_output_handler - Callback function converts character encoding in output buffer
* - mb_scrub - Replaces ill-formed byte sequences with substitute characters
* - mb_strlen - Get string length
* - mb_strpos - Find position of first occurrence of string in a string
* - mb_strrpos - Find position of last occurrence of a string in a string
* - mb_str_split - Convert a string to an array
* - mb_strtolower - Make a string lowercase
* - mb_strtoupper - Make a string uppercase
* - mb_substitute_character - Set/Get substitution character
* - mb_substr - Get part of string
* - mb_stripos - Finds position of first occurrence of a string within another, case insensitive
* - mb_stristr - Finds first occurrence of a string within another, case insensitive
* - mb_strrchr - Finds the last occurrence of a character in a string within another
* - mb_strrichr - Finds the last occurrence of a character in a string within another, case insensitive
* - mb_strripos - Finds position of last occurrence of a string within another, case insensitive
* - mb_strstr - Finds first occurrence of a string within another
* - mb_strwidth - Return width of string
* - mb_substr_count - Count the number of substring occurrences
*
* Not implemented:
* - mb_convert_kana - Convert "kana" one from another ("zen-kaku", "han-kaku" and more)
* - mb_ereg_* - Regular expression with multibyte support
* - mb_parse_str - Parse GET/POST/COOKIE data and set global variable
* - mb_preferred_mime_name - Get MIME charset string
* - mb_regex_encoding - Returns current encoding for multibyte regex as string
* - mb_regex_set_options - Set/Get the default options for mbregex functions
* - mb_send_mail - Send encoded mail
* - mb_split - Split multibyte string using regular expression
* - mb_strcut - Get part of string
* - mb_strimwidth - Get truncated string with specified width
*
* @author Nicolas Grekas <p@tchwork.com>
*
* @internal
*/
final class Mbstring
{
const MB_CASE_FOLD = PHP_INT_MAX;
private static $encodingList = array('ASCII', 'UTF-8');
private static $language = 'neutral';
private static $internalEncoding = 'UTF-8';
private static $caseFold = array(
array('µ', 'ſ', "\xCD\x85", 'ς', "\xCF\x90", "\xCF\x91", "\xCF\x95", "\xCF\x96", "\xCF\xB0", "\xCF\xB1", "\xCF\xB5", "\xE1\xBA\x9B", "\xE1\xBE\xBE"),
array('μ', 's', 'ι', 'σ', 'β', 'θ', 'φ', 'π', 'κ', 'ρ', 'ε', "\xE1\xB9\xA1", 'ι'),
);
public static function mb_convert_encoding($s, $toEncoding, $fromEncoding = null)
{
if (\is_array($fromEncoding) || false !== strpos($fromEncoding, ',')) {
$fromEncoding = self::mb_detect_encoding($s, $fromEncoding);
} else {
$fromEncoding = self::getEncoding($fromEncoding);
}
$toEncoding = self::getEncoding($toEncoding);
if ('BASE64' === $fromEncoding) {
$s = base64_decode($s);
$fromEncoding = $toEncoding;
}
if ('BASE64' === $toEncoding) {
return base64_encode($s);
}
if ('HTML-ENTITIES' === $toEncoding || 'HTML' === $toEncoding) {
if ('HTML-ENTITIES' === $fromEncoding || 'HTML' === $fromEncoding) {
$fromEncoding = 'Windows-1252';
}
if ('UTF-8' !== $fromEncoding) {
$s = iconv($fromEncoding, 'UTF-8//IGNORE', $s);
}
return preg_replace_callback('/[\x80-\xFF]+/', array(__CLASS__, 'html_encoding_callback'), $s);
}
if ('HTML-ENTITIES' === $fromEncoding) {
$s = html_entity_decode($s, ENT_COMPAT, 'UTF-8');
$fromEncoding = 'UTF-8';
}
return iconv($fromEncoding, $toEncoding.'//IGNORE', $s);
}
public static function mb_convert_variables($toEncoding, $fromEncoding, &...$vars)
{
$ok = true;
array_walk_recursive($vars, function (&$v) use (&$ok, $toEncoding, $fromEncoding) {
if (false === $v = Mbstring::mb_convert_encoding($v, $toEncoding, $fromEncoding)) {
$ok = false;
}
});
return $ok ? $fromEncoding : false;
}
public static function mb_decode_mimeheader($s)
{
return iconv_mime_decode($s, 2, self::$internalEncoding);
}
public static function mb_encode_mimeheader($s, $charset = null, $transferEncoding = null, $linefeed = null, $indent = null)
{
trigger_error('mb_encode_mimeheader() is bugged. Please use iconv_mime_encode() instead', E_USER_WARNING);
}
public static function mb_decode_numericentity($s, $convmap, $encoding = null)
{
if (null !== $s && !\is_scalar($s) && !(\is_object($s) && \method_exists($s, '__toString'))) {
trigger_error('mb_decode_numericentity() expects parameter 1 to be string, '.\gettype($s).' given', E_USER_WARNING);
return null;
}
if (!\is_array($convmap) || !$convmap) {
return false;
}
if (null !== $encoding && !\is_scalar($encoding)) {
trigger_error('mb_decode_numericentity() expects parameter 3 to be string, '.\gettype($s).' given', E_USER_WARNING);
return ''; // Instead of null (cf. mb_encode_numericentity).
}
$s = (string) $s;
if ('' === $s) {
return '';
}
$encoding = self::getEncoding($encoding);
if ('UTF-8' === $encoding) {
$encoding = null;
if (!preg_match('//u', $s)) {
$s = @iconv('UTF-8', 'UTF-8//IGNORE', $s);
}
} else {
$s = iconv($encoding, 'UTF-8//IGNORE', $s);
}
$cnt = floor(\count($convmap) / 4) * 4;
for ($i = 0; $i < $cnt; $i += 4) {
// collector_decode_htmlnumericentity ignores $convmap[$i + 3]
$convmap[$i] += $convmap[$i + 2];
$convmap[$i + 1] += $convmap[$i + 2];
}
$s = preg_replace_callback('/&#(?:0*([0-9]+)|x0*([0-9a-fA-F]+))(?!&);?/', function (array $m) use ($cnt, $convmap) {
$c = isset($m[2]) ? (int) hexdec($m[2]) : $m[1];
for ($i = 0; $i < $cnt; $i += 4) {
if ($c >= $convmap[$i] && $c <= $convmap[$i + 1]) {
return Mbstring::mb_chr($c - $convmap[$i + 2]);
}
}
return $m[0];
}, $s);
if (null === $encoding) {
return $s;
}
return iconv('UTF-8', $encoding.'//IGNORE', $s);
}
public static function mb_encode_numericentity($s, $convmap, $encoding = null, $is_hex = false)
{
if (null !== $s && !\is_scalar($s) && !(\is_object($s) && \method_exists($s, '__toString'))) {
trigger_error('mb_encode_numericentity() expects parameter 1 to be string, '.\gettype($s).' given', E_USER_WARNING);
return null;
}
if (!\is_array($convmap) || !$convmap) {
return false;
}
if (null !== $encoding && !\is_scalar($encoding)) {
trigger_error('mb_encode_numericentity() expects parameter 3 to be string, '.\gettype($s).' given', E_USER_WARNING);
return null; // Instead of '' (cf. mb_decode_numericentity).
}
if (null !== $is_hex && !\is_scalar($is_hex)) {
trigger_error('mb_encode_numericentity() expects parameter 4 to be boolean, '.\gettype($s).' given', E_USER_WARNING);
return null;
}
$s = (string) $s;
if ('' === $s) {
return '';
}
$encoding = self::getEncoding($encoding);
if ('UTF-8' === $encoding) {
$encoding = null;
if (!preg_match('//u', $s)) {
$s = @iconv('UTF-8', 'UTF-8//IGNORE', $s);
}
} else {
$s = iconv($encoding, 'UTF-8//IGNORE', $s);
}
static $ulenMask = array("\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4);
$cnt = floor(\count($convmap) / 4) * 4;
$i = 0;
$len = \strlen($s);
$result = '';
while ($i < $len) {
$ulen = $s[$i] < "\x80" ? 1 : $ulenMask[$s[$i] & "\xF0"];
$uchr = substr($s, $i, $ulen);
$i += $ulen;
$c = self::mb_ord($uchr);
for ($j = 0; $j < $cnt; $j += 4) {
if ($c >= $convmap[$j] && $c <= $convmap[$j + 1]) {
$cOffset = ($c + $convmap[$j + 2]) & $convmap[$j + 3];
$result .= $is_hex ? sprintf('&#x%X;', $cOffset) : '&#'.$cOffset.';';
continue 2;
}
}
$result .= $uchr;
}
if (null === $encoding) {
return $result;
}
return iconv('UTF-8', $encoding.'//IGNORE', $result);
}
public static function mb_convert_case($s, $mode, $encoding = null)
{
$s = (string) $s;
if ('' === $s) {
return '';
}
$encoding = self::getEncoding($encoding);
if ('UTF-8' === $encoding) {
$encoding = null;
if (!preg_match('//u', $s)) {
$s = @iconv('UTF-8', 'UTF-8//IGNORE', $s);
}
} else {
$s = iconv($encoding, 'UTF-8//IGNORE', $s);
}
if (MB_CASE_TITLE == $mode) {
static $titleRegexp = null;
if (null === $titleRegexp) {
$titleRegexp = self::getData('titleCaseRegexp');
}
$s = preg_replace_callback($titleRegexp, array(__CLASS__, 'title_case'), $s);
} else {
if (MB_CASE_UPPER == $mode) {
static $upper = null;
if (null === $upper) {
$upper = self::getData('upperCase');
}
$map = $upper;
} else {
if (self::MB_CASE_FOLD === $mode) {
$s = str_replace(self::$caseFold[0], self::$caseFold[1], $s);
}
static $lower = null;
if (null === $lower) {
$lower = self::getData('lowerCase');
}
$map = $lower;
}
static $ulenMask = array("\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4);
$i = 0;
$len = \strlen($s);
while ($i < $len) {
$ulen = $s[$i] < "\x80" ? 1 : $ulenMask[$s[$i] & "\xF0"];
$uchr = substr($s, $i, $ulen);
$i += $ulen;
if (isset($map[$uchr])) {
$uchr = $map[$uchr];
$nlen = \strlen($uchr);
if ($nlen == $ulen) {
$nlen = $i;
do {
$s[--$nlen] = $uchr[--$ulen];
} while ($ulen);
} else {
$s = substr_replace($s, $uchr, $i - $ulen, $ulen);
$len += $nlen - $ulen;
$i += $nlen - $ulen;
}
}
}
}
if (null === $encoding) {
return $s;
}
return iconv('UTF-8', $encoding.'//IGNORE', $s);
}
public static function mb_internal_encoding($encoding = null)
{
if (null === $encoding) {
return self::$internalEncoding;
}
$encoding = self::getEncoding($encoding);
if ('UTF-8' === $encoding || false !== @iconv($encoding, $encoding, ' ')) {
self::$internalEncoding = $encoding;
return true;
}
return false;
}
public static function mb_language($lang = null)
{
if (null === $lang) {
return self::$language;
}
switch ($lang = strtolower($lang)) {
case 'uni':
case 'neutral':
self::$language = $lang;
return true;
}
return false;
}
public static function mb_list_encodings()
{
return array('UTF-8');
}
public static function mb_encoding_aliases($encoding)
{
switch (strtoupper($encoding)) {
case 'UTF8':
case 'UTF-8':
return array('utf8');
}
return false;
}
public static function mb_check_encoding($var = null, $encoding = null)
{
if (null === $encoding) {
if (null === $var) {
return false;
}
$encoding = self::$internalEncoding;
}
return self::mb_detect_encoding($var, array($encoding)) || false !== @iconv($encoding, $encoding, $var);
}
public static function mb_detect_encoding($str, $encodingList = null, $strict = false)
{
if (null === $encodingList) {
$encodingList = self::$encodingList;
} else {
if (!\is_array($encodingList)) {
$encodingList = array_map('trim', explode(',', $encodingList));
}
$encodingList = array_map('strtoupper', $encodingList);
}
foreach ($encodingList as $enc) {
switch ($enc) {
case 'ASCII':
if (!preg_match('/[\x80-\xFF]/', $str)) {
return $enc;
}
break;
case 'UTF8':
case 'UTF-8':
if (preg_match('//u', $str)) {
return 'UTF-8';
}
break;
default:
if (0 === strncmp($enc, 'ISO-8859-', 9)) {
return $enc;
}
}
}
return false;
}
public static function mb_detect_order($encodingList = null)
{
if (null === $encodingList) {
return self::$encodingList;
}
if (!\is_array($encodingList)) {
$encodingList = array_map('trim', explode(',', $encodingList));
}
$encodingList = array_map('strtoupper', $encodingList);
foreach ($encodingList as $enc) {
switch ($enc) {
default:
if (strncmp($enc, 'ISO-8859-', 9)) {
return false;
}
// no break
case 'ASCII':
case 'UTF8':
case 'UTF-8':
}
}
self::$encodingList = $encodingList;
return true;
}
public static function mb_strlen($s, $encoding = null)
{
$encoding = self::getEncoding($encoding);
if ('CP850' === $encoding || 'ASCII' === $encoding) {
return \strlen($s);
}
return @iconv_strlen($s, $encoding);
}
public static function mb_strpos($haystack, $needle, $offset = 0, $encoding = null)
{
$encoding = self::getEncoding($encoding);
if ('CP850' === $encoding || 'ASCII' === $encoding) {
return strpos($haystack, $needle, $offset);
}
$needle = (string) $needle;
if ('' === $needle) {
trigger_error(__METHOD__.': Empty delimiter', E_USER_WARNING);
return false;
}
return iconv_strpos($haystack, $needle, $offset, $encoding);
}
public static function mb_strrpos($haystack, $needle, $offset = 0, $encoding = null)
{
$encoding = self::getEncoding($encoding);
if ('CP850' === $encoding || 'ASCII' === $encoding) {
return strrpos($haystack, $needle, $offset);
}
if ($offset != (int) $offset) {
$offset = 0;
} elseif ($offset = (int) $offset) {
if ($offset < 0) {
if (0 > $offset += self::mb_strlen($needle)) {
$haystack = self::mb_substr($haystack, 0, $offset, $encoding);
}
$offset = 0;
} else {
$haystack = self::mb_substr($haystack, $offset, 2147483647, $encoding);
}
}
$pos = iconv_strrpos($haystack, $needle, $encoding);
return false !== $pos ? $offset + $pos : false;
}
public static function mb_str_split($string, $split_length = 1, $encoding = null)
{
if (null !== $string && !\is_scalar($string) && !(\is_object($string) && \method_exists($string, '__toString'))) {
trigger_error('mb_str_split() expects parameter 1 to be string, '.\gettype($string).' given', E_USER_WARNING);
return null;
}
if (1 > $split_length = (int) $split_length) {
trigger_error('The length of each segment must be greater than zero', E_USER_WARNING);
return false;
}
if (null === $encoding) {
$encoding = mb_internal_encoding();
}
if ('UTF-8' === $encoding = self::getEncoding($encoding)) {
$rx = '/(';
while (65535 < $split_length) {
$rx .= '.{65535}';
$split_length -= 65535;
}
$rx .= '.{'.$split_length.'})/us';
return preg_split($rx, $string, null, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
}
$result = array();
$length = mb_strlen($string, $encoding);
for ($i = 0; $i < $length; $i += $split_length) {
$result[] = mb_substr($string, $i, $split_length, $encoding);
}
return $result;
}
public static function mb_strtolower($s, $encoding = null)
{
return self::mb_convert_case($s, MB_CASE_LOWER, $encoding);
}
public static function mb_strtoupper($s, $encoding = null)
{
return self::mb_convert_case($s, MB_CASE_UPPER, $encoding);
}
public static function mb_substitute_character($c = null)
{
if (0 === strcasecmp($c, 'none')) {
return true;
}
return null !== $c ? false : 'none';
}
public static function mb_substr($s, $start, $length = null, $encoding = null)
{
$encoding = self::getEncoding($encoding);
if ('CP850' === $encoding || 'ASCII' === $encoding) {
return (string) substr($s, $start, null === $length ? 2147483647 : $length);
}
if ($start < 0) {
$start = iconv_strlen($s, $encoding) + $start;
if ($start < 0) {
$start = 0;
}
}
if (null === $length) {
$length = 2147483647;
} elseif ($length < 0) {
$length = iconv_strlen($s, $encoding) + $length - $start;
if ($length < 0) {
return '';
}
}
return (string) iconv_substr($s, $start, $length, $encoding);
}
public static function mb_stripos($haystack, $needle, $offset = 0, $encoding = null)
{
$haystack = self::mb_convert_case($haystack, self::MB_CASE_FOLD, $encoding);
$needle = self::mb_convert_case($needle, self::MB_CASE_FOLD, $encoding);
return self::mb_strpos($haystack, $needle, $offset, $encoding);
}
public static function mb_stristr($haystack, $needle, $part = false, $encoding = null)
{
$pos = self::mb_stripos($haystack, $needle, 0, $encoding);
return self::getSubpart($pos, $part, $haystack, $encoding);
}
public static function mb_strrchr($haystack, $needle, $part = false, $encoding = null)
{
$encoding = self::getEncoding($encoding);
if ('CP850' === $encoding || 'ASCII' === $encoding) {
$pos = strrpos($haystack, $needle);
} else {
$needle = self::mb_substr($needle, 0, 1, $encoding);
$pos = iconv_strrpos($haystack, $needle, $encoding);
}
return self::getSubpart($pos, $part, $haystack, $encoding);
}
public static function mb_strrichr($haystack, $needle, $part = false, $encoding = null)
{
$needle = self::mb_substr($needle, 0, 1, $encoding);
$pos = self::mb_strripos($haystack, $needle, $encoding);
return self::getSubpart($pos, $part, $haystack, $encoding);
}
public static function mb_strripos($haystack, $needle, $offset = 0, $encoding = null)
{
$haystack = self::mb_convert_case($haystack, self::MB_CASE_FOLD, $encoding);
$needle = self::mb_convert_case($needle, self::MB_CASE_FOLD, $encoding);
return self::mb_strrpos($haystack, $needle, $offset, $encoding);
}
public static function mb_strstr($haystack, $needle, $part = false, $encoding = null)
{
$pos = strpos($haystack, $needle);
if (false === $pos) {
return false;
}
if ($part) {
return substr($haystack, 0, $pos);
}
return substr($haystack, $pos);
}
public static function mb_get_info($type = 'all')
{
$info = array(
'internal_encoding' => self::$internalEncoding,
'http_output' => 'pass',
'http_output_conv_mimetypes' => '^(text/|application/xhtml\+xml)',
'func_overload' => 0,
'func_overload_list' => 'no overload',
'mail_charset' => 'UTF-8',
'mail_header_encoding' => 'BASE64',
'mail_body_encoding' => 'BASE64',
'illegal_chars' => 0,
'encoding_translation' => 'Off',
'language' => self::$language,
'detect_order' => self::$encodingList,
'substitute_character' => 'none',
'strict_detection' => 'Off',
);
if ('all' === $type) {
return $info;
}
if (isset($info[$type])) {
return $info[$type];
}
return false;
}
public static function mb_http_input($type = '')
{
return false;
}
public static function mb_http_output($encoding = null)
{
return null !== $encoding ? 'pass' === $encoding : 'pass';
}
public static function mb_strwidth($s, $encoding = null)
{
$encoding = self::getEncoding($encoding);
if ('UTF-8' !== $encoding) {
$s = iconv($encoding, 'UTF-8//IGNORE', $s);
}
$s = preg_replace('/[\x{1100}-\x{115F}\x{2329}\x{232A}\x{2E80}-\x{303E}\x{3040}-\x{A4CF}\x{AC00}-\x{D7A3}\x{F900}-\x{FAFF}\x{FE10}-\x{FE19}\x{FE30}-\x{FE6F}\x{FF00}-\x{FF60}\x{FFE0}-\x{FFE6}\x{20000}-\x{2FFFD}\x{30000}-\x{3FFFD}]/u', '', $s, -1, $wide);
return ($wide << 1) + iconv_strlen($s, 'UTF-8');
}
public static function mb_substr_count($haystack, $needle, $encoding = null)
{
return substr_count($haystack, $needle);
}
public static function mb_output_handler($contents, $status)
{
return $contents;
}
public static function mb_chr($code, $encoding = null)
{
if (0x80 > $code %= 0x200000) {
$s = \chr($code);
} elseif (0x800 > $code) {
$s = \chr(0xC0 | $code >> 6).\chr(0x80 | $code & 0x3F);
} elseif (0x10000 > $code) {
$s = \chr(0xE0 | $code >> 12).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F);
} else {
$s = \chr(0xF0 | $code >> 18).\chr(0x80 | $code >> 12 & 0x3F).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F);
}
if ('UTF-8' !== $encoding = self::getEncoding($encoding)) {
$s = mb_convert_encoding($s, $encoding, 'UTF-8');
}
return $s;
}
public static function mb_ord($s, $encoding = null)
{
if ('UTF-8' !== $encoding = self::getEncoding($encoding)) {
$s = mb_convert_encoding($s, 'UTF-8', $encoding);
}
if (1 === \strlen($s)) {
return \ord($s);
}
$code = ($s = unpack('C*', substr($s, 0, 4))) ? $s[1] : 0;
if (0xF0 <= $code) {
return (($code - 0xF0) << 18) + (($s[2] - 0x80) << 12) + (($s[3] - 0x80) << 6) + $s[4] - 0x80;
}
if (0xE0 <= $code) {
return (($code - 0xE0) << 12) + (($s[2] - 0x80) << 6) + $s[3] - 0x80;
}
if (0xC0 <= $code) {
return (($code - 0xC0) << 6) + $s[2] - 0x80;
}
return $code;
}
private static function getSubpart($pos, $part, $haystack, $encoding)
{
if (false === $pos) {
return false;
}
if ($part) {
return self::mb_substr($haystack, 0, $pos, $encoding);
}
return self::mb_substr($haystack, $pos, null, $encoding);
}
private static function html_encoding_callback(array $m)
{
$i = 1;
$entities = '';
$m = unpack('C*', htmlentities($m[0], ENT_COMPAT, 'UTF-8'));
while (isset($m[$i])) {
if (0x80 > $m[$i]) {
$entities .= \chr($m[$i++]);
continue;
}
if (0xF0 <= $m[$i]) {
$c = (($m[$i++] - 0xF0) << 18) + (($m[$i++] - 0x80) << 12) + (($m[$i++] - 0x80) << 6) + $m[$i++] - 0x80;
} elseif (0xE0 <= $m[$i]) {
$c = (($m[$i++] - 0xE0) << 12) + (($m[$i++] - 0x80) << 6) + $m[$i++] - 0x80;
} else {
$c = (($m[$i++] - 0xC0) << 6) + $m[$i++] - 0x80;
}
$entities .= '&#'.$c.';';
}
return $entities;
}
private static function title_case(array $s)
{
return self::mb_convert_case($s[1], MB_CASE_UPPER, 'UTF-8').self::mb_convert_case($s[2], MB_CASE_LOWER, 'UTF-8');
}
private static function getData($file)
{
if (file_exists($file = __DIR__.'/Resources/unidata/'.$file.'.php')) {
return require $file;
}
return false;
}
private static function getEncoding($encoding)
{
if (null === $encoding) {
return self::$internalEncoding;
}
if ('UTF-8' === $encoding) {
return 'UTF-8';
}
$encoding = strtoupper($encoding);
if ('8BIT' === $encoding || 'BINARY' === $encoding) {
return 'CP850';
}
if ('UTF8' === $encoding) {
return 'UTF-8';
}
return $encoding;
}
}
<?php
return array (
'a' => 'A',
'b' => 'B',
'c' => 'C',
'd' => 'D',
'e' => 'E',
'f' => 'F',
'g' => 'G',
'h' => 'H',
'i' => 'I',
'j' => 'J',
'k' => 'K',
'l' => 'L',
'm' => 'M',
'n' => 'N',
'o' => 'O',
'p' => 'P',
'q' => 'Q',
'r' => 'R',
's' => 'S',
't' => 'T',
'u' => 'U',
'v' => 'V',
'w' => 'W',
'x' => 'X',
'y' => 'Y',
'z' => 'Z',
'µ' => 'Μ',
'à' => 'À',
'á' => 'Á',
'â' => 'Â',
'ã' => 'Ã',
'ä' => 'Ä',
'å' => 'Å',
'æ' => 'Æ',
'ç' => 'Ç',
'è' => 'È',
'é' => 'É',
'ê' => 'Ê',
'ë' => 'Ë',
'ì' => 'Ì',
'í' => 'Í',
'î' => 'Î',
'ï' => 'Ï',
'ð' => 'Ð',
'ñ' => 'Ñ',
'ò' => 'Ò',
'ó' => 'Ó',
'ô' => 'Ô',
'õ' => 'Õ',
'ö' => 'Ö',
'ø' => 'Ø',
'ù' => 'Ù',
'ú' => 'Ú',
'û' => 'Û',
'ü' => 'Ü',
'ý' => 'Ý',
'þ' => 'Þ',
'ÿ' => 'Ÿ',
'ā' => 'Ā',
'ă' => 'Ă',
'ą' => 'Ą',
'ć' => 'Ć',
'ĉ' => 'Ĉ',
'ċ' => 'Ċ',
'č' => 'Č',
'ď' => 'Ď',
'đ' => 'Đ',
'ē' => 'Ē',
'ĕ' => 'Ĕ',
'ė' => 'Ė',
'ę' => 'Ę',
'ě' => 'Ě',
'ĝ' => 'Ĝ',
'ğ' => 'Ğ',
'ġ' => 'Ġ',
'ģ' => 'Ģ',
'ĥ' => 'Ĥ',
'ħ' => 'Ħ',
'ĩ' => 'Ĩ',
'ī' => 'Ī',
'ĭ' => 'Ĭ',
'į' => 'Į',
'ı' => 'I',
'ij' => 'IJ',
'ĵ' => 'Ĵ',
'ķ' => 'Ķ',
'ĺ' => 'Ĺ',
'ļ' => 'Ļ',
'ľ' => 'Ľ',
'ŀ' => 'Ŀ',
'ł' => 'Ł',
'ń' => 'Ń',
'ņ' => 'Ņ',
'ň' => 'Ň',
'ŋ' => 'Ŋ',
'ō' => 'Ō',
'ŏ' => 'Ŏ',
'ő' => 'Ő',
'œ' => 'Œ',
'ŕ' => 'Ŕ',
'ŗ' => 'Ŗ',
'ř' => 'Ř',
'ś' => 'Ś',
'ŝ' => 'Ŝ',
'ş' => 'Ş',
'š' => 'Š',
'ţ' => 'Ţ',
'ť' => 'Ť',
'ŧ' => 'Ŧ',
'ũ' => 'Ũ',
'ū' => 'Ū',
'ŭ' => 'Ŭ',
'ů' => 'Ů',
'ű' => 'Ű',
'ų' => 'Ų',
'ŵ' => 'Ŵ',
'ŷ' => 'Ŷ',
'ź' => 'Ź',
'ż' => 'Ż',
'ž' => 'Ž',
'ſ' => 'S',
'ƀ' => 'Ƀ',
'ƃ' => 'Ƃ',
'ƅ' => 'Ƅ',
'ƈ' => 'Ƈ',
'ƌ' => 'Ƌ',
'ƒ' => 'Ƒ',
'ƕ' => 'Ƕ',
'ƙ' => 'Ƙ',
'ƚ' => 'Ƚ',
'ƞ' => 'Ƞ',
'ơ' => 'Ơ',
'ƣ' => 'Ƣ',
'ƥ' => 'Ƥ',
'ƨ' => 'Ƨ',
'ƭ' => 'Ƭ',
'ư' => 'Ư',
'ƴ' => 'Ƴ',
'ƶ' => 'Ƶ',
'ƹ' => 'Ƹ',
'ƽ' => 'Ƽ',
'ƿ' => 'Ƿ',
'Dž' => 'DŽ',
'dž' => 'DŽ',
'Lj' => 'LJ',
'lj' => 'LJ',
'Nj' => 'NJ',
'nj' => 'NJ',
'ǎ' => 'Ǎ',
'ǐ' => 'Ǐ',
'ǒ' => 'Ǒ',
'ǔ' => 'Ǔ',
'ǖ' => 'Ǖ',
'ǘ' => 'Ǘ',
'ǚ' => 'Ǚ',
'ǜ' => 'Ǜ',
'ǝ' => 'Ǝ',
'ǟ' => 'Ǟ',
'ǡ' => 'Ǡ',
'ǣ' => 'Ǣ',
'ǥ' => 'Ǥ',
'ǧ' => 'Ǧ',
'ǩ' => 'Ǩ',
'ǫ' => 'Ǫ',
'ǭ' => 'Ǭ',
'ǯ' => 'Ǯ',
'Dz' => 'DZ',
'dz' => 'DZ',
'ǵ' => 'Ǵ',
'ǹ' => 'Ǹ',
'ǻ' => 'Ǻ',
'ǽ' => 'Ǽ',
'ǿ' => 'Ǿ',
'ȁ' => 'Ȁ',
'ȃ' => 'Ȃ',
'ȅ' => 'Ȅ',
'ȇ' => 'Ȇ',
'ȉ' => 'Ȉ',
'ȋ' => 'Ȋ',
'ȍ' => 'Ȍ',
'ȏ' => 'Ȏ',
'ȑ' => 'Ȑ',
'ȓ' => 'Ȓ',
'ȕ' => 'Ȕ',
'ȗ' => 'Ȗ',
'ș' => 'Ș',
'ț' => 'Ț',
'ȝ' => 'Ȝ',
'ȟ' => 'Ȟ',
'ȣ' => 'Ȣ',
'ȥ' => 'Ȥ',
'ȧ' => 'Ȧ',
'ȩ' => 'Ȩ',
'ȫ' => 'Ȫ',
'ȭ' => 'Ȭ',
'ȯ' => 'Ȯ',
'ȱ' => 'Ȱ',
'ȳ' => 'Ȳ',
'ȼ' => 'Ȼ',
'ȿ' => 'Ȿ',
'ɀ' => 'Ɀ',
'ɂ' => 'Ɂ',
'ɇ' => 'Ɇ',
'ɉ' => 'Ɉ',
'ɋ' => 'Ɋ',
'ɍ' => 'Ɍ',
'ɏ' => 'Ɏ',
'ɐ' => 'Ɐ',
'ɑ' => 'Ɑ',
'ɒ' => 'Ɒ',
'ɓ' => 'Ɓ',
'ɔ' => 'Ɔ',
'ɖ' => 'Ɖ',
'ɗ' => 'Ɗ',
'ə' => 'Ə',
'ɛ' => 'Ɛ',
'ɜ' => '',
'ɠ' => 'Ɠ',
'ɡ' => 'Ɡ',
'ɣ' => 'Ɣ',
'ɥ' => 'Ɥ',
'ɦ' => 'Ɦ',
'ɨ' => 'Ɨ',
'ɩ' => 'Ɩ',
'ɪ' => 'Ɪ',
'ɫ' => 'Ɫ',
'ɬ' => 'Ɬ',
'ɯ' => 'Ɯ',
'ɱ' => 'Ɱ',
'ɲ' => 'Ɲ',
'ɵ' => 'Ɵ',
'ɽ' => 'Ɽ',
'ʀ' => 'Ʀ',
'ʂ' => 'Ʂ',
'ʃ' => 'Ʃ',
'ʇ' => 'Ʇ',
'ʈ' => 'Ʈ',
'ʉ' => 'Ʉ',
'ʊ' => 'Ʊ',
'ʋ' => 'Ʋ',
'ʌ' => 'Ʌ',
'ʒ' => 'Ʒ',
'ʝ' => '',
'ʞ' => 'Ʞ',
'ͅ' => 'Ι',
'ͱ' => 'Ͱ',
'ͳ' => 'Ͳ',
'ͷ' => 'Ͷ',
'ͻ' => 'Ͻ',
'ͼ' => 'Ͼ',
'ͽ' => 'Ͽ',
'ά' => 'Ά',
'έ' => 'Έ',
'ή' => 'Ή',
'ί' => 'Ί',
'α' => 'Α',
'β' => 'Β',
'γ' => 'Γ',
'δ' => 'Δ',
'ε' => 'Ε',
'ζ' => 'Ζ',
'η' => 'Η',
'θ' => 'Θ',
'ι' => 'Ι',
'κ' => 'Κ',
'λ' => 'Λ',
'μ' => 'Μ',
'ν' => 'Ν',
'ξ' => 'Ξ',
'ο' => 'Ο',
'π' => 'Π',
'ρ' => 'Ρ',
'ς' => 'Σ',
'σ' => 'Σ',
'τ' => 'Τ',
'υ' => 'Υ',
'φ' => 'Φ',
'χ' => 'Χ',
'ψ' => 'Ψ',
'ω' => 'Ω',
'ϊ' => 'Ϊ',
'ϋ' => 'Ϋ',
'ό' => 'Ό',
'ύ' => 'Ύ',
'ώ' => 'Ώ',
'ϐ' => 'Β',
'ϑ' => 'Θ',
'ϕ' => 'Φ',
'ϖ' => 'Π',
'ϗ' => 'Ϗ',
'ϙ' => 'Ϙ',
'ϛ' => 'Ϛ',
'ϝ' => 'Ϝ',
'ϟ' => 'Ϟ',
'ϡ' => 'Ϡ',
'ϣ' => 'Ϣ',
'ϥ' => 'Ϥ',
'ϧ' => 'Ϧ',
'ϩ' => 'Ϩ',
'ϫ' => 'Ϫ',
'ϭ' => 'Ϭ',
'ϯ' => 'Ϯ',
'ϰ' => 'Κ',
'ϱ' => 'Ρ',
'ϲ' => 'Ϲ',
'ϳ' => 'Ϳ',
'ϵ' => 'Ε',
'ϸ' => 'Ϸ',
'ϻ' => 'Ϻ',
'а' => 'А',
'б' => 'Б',
'в' => 'В',
'г' => 'Г',
'д' => 'Д',
'е' => 'Е',
'ж' => 'Ж',
'з' => 'З',
'и' => 'И',
'й' => 'Й',
'к' => 'К',
'л' => 'Л',
'м' => 'М',
'н' => 'Н',
'о' => 'О',
'п' => 'П',
'р' => 'Р',
'с' => 'С',
'т' => 'Т',
'у' => 'У',
'ф' => 'Ф',
'х' => 'Х',
'ц' => 'Ц',
'ч' => 'Ч',
'ш' => 'Ш',
'щ' => 'Щ',
'ъ' => 'Ъ',
'ы' => 'Ы',
'ь' => 'Ь',
'э' => 'Э',
'ю' => 'Ю',
'я' => 'Я',
'ѐ' => 'Ѐ',
'ё' => 'Ё',
'ђ' => 'Ђ',
'ѓ' => 'Ѓ',
'є' => 'Є',
'ѕ' => 'Ѕ',
'і' => 'І',
'ї' => 'Ї',
'ј' => 'Ј',
'љ' => 'Љ',
'њ' => 'Њ',
'ћ' => 'Ћ',
'ќ' => 'Ќ',
'ѝ' => 'Ѝ',
'ў' => 'Ў',
'џ' => 'Џ',
'ѡ' => 'Ѡ',
'ѣ' => 'Ѣ',
'ѥ' => 'Ѥ',
'ѧ' => 'Ѧ',
'ѩ' => 'Ѩ',
'ѫ' => 'Ѫ',
'ѭ' => 'Ѭ',
'ѯ' => 'Ѯ',
'ѱ' => 'Ѱ',
'ѳ' => 'Ѳ',
'ѵ' => 'Ѵ',
'ѷ' => 'Ѷ',
'ѹ' => 'Ѹ',
'ѻ' => 'Ѻ',
'ѽ' => 'Ѽ',
'ѿ' => 'Ѿ',
'ҁ' => 'Ҁ',
'ҋ' => 'Ҋ',
'ҍ' => 'Ҍ',
'ҏ' => 'Ҏ',
'ґ' => 'Ґ',
'ғ' => 'Ғ',
'ҕ' => 'Ҕ',
'җ' => 'Җ',
'ҙ' => 'Ҙ',
'қ' => 'Қ',
'ҝ' => 'Ҝ',
'ҟ' => 'Ҟ',
'ҡ' => 'Ҡ',
'ң' => 'Ң',
'ҥ' => 'Ҥ',
'ҧ' => 'Ҧ',
'ҩ' => 'Ҩ',
'ҫ' => 'Ҫ',
'ҭ' => 'Ҭ',
'ү' => 'Ү',
'ұ' => 'Ұ',
'ҳ' => 'Ҳ',
'ҵ' => 'Ҵ',
'ҷ' => 'Ҷ',
'ҹ' => 'Ҹ',
'һ' => 'Һ',
'ҽ' => 'Ҽ',
'ҿ' => 'Ҿ',
'ӂ' => 'Ӂ',
'ӄ' => 'Ӄ',
'ӆ' => 'Ӆ',
'ӈ' => 'Ӈ',
'ӊ' => 'Ӊ',
'ӌ' => 'Ӌ',
'ӎ' => 'Ӎ',
'ӏ' => 'Ӏ',
'ӑ' => 'Ӑ',
'ӓ' => 'Ӓ',
'ӕ' => 'Ӕ',
'ӗ' => 'Ӗ',
'ә' => 'Ә',
'ӛ' => 'Ӛ',
'ӝ' => 'Ӝ',
'ӟ' => 'Ӟ',
'ӡ' => 'Ӡ',
'ӣ' => 'Ӣ',
'ӥ' => 'Ӥ',
'ӧ' => 'Ӧ',
'ө' => 'Ө',
'ӫ' => 'Ӫ',
'ӭ' => 'Ӭ',
'ӯ' => 'Ӯ',
'ӱ' => 'Ӱ',
'ӳ' => 'Ӳ',
'ӵ' => 'Ӵ',
'ӷ' => 'Ӷ',
'ӹ' => 'Ӹ',
'ӻ' => 'Ӻ',
'ӽ' => 'Ӽ',
'ӿ' => 'Ӿ',
'ԁ' => 'Ԁ',
'ԃ' => 'Ԃ',
'ԅ' => 'Ԅ',
'ԇ' => 'Ԇ',
'ԉ' => 'Ԉ',
'ԋ' => 'Ԋ',
'ԍ' => 'Ԍ',
'ԏ' => 'Ԏ',
'ԑ' => 'Ԑ',
'ԓ' => 'Ԓ',
'ԕ' => 'Ԕ',
'ԗ' => 'Ԗ',
'ԙ' => 'Ԙ',
'ԛ' => 'Ԛ',
'ԝ' => 'Ԝ',
'ԟ' => 'Ԟ',
'ԡ' => 'Ԡ',
'ԣ' => 'Ԣ',
'ԥ' => 'Ԥ',
'ԧ' => 'Ԧ',
'ԩ' => 'Ԩ',
'ԫ' => 'Ԫ',
'ԭ' => 'Ԭ',
'ԯ' => 'Ԯ',
'ա' => 'Ա',
'բ' => 'Բ',
'գ' => 'Գ',
'դ' => 'Դ',
'ե' => 'Ե',
'զ' => 'Զ',
'է' => 'Է',
'ը' => 'Ը',
'թ' => 'Թ',
'ժ' => 'Ժ',
'ի' => 'Ի',
'լ' => 'Լ',
'խ' => 'Խ',
'ծ' => 'Ծ',
'կ' => 'Կ',
'հ' => 'Հ',
'ձ' => 'Ձ',
'ղ' => 'Ղ',
'ճ' => 'Ճ',
'մ' => 'Մ',
'յ' => 'Յ',
'ն' => 'Ն',
'շ' => 'Շ',
'ո' => 'Ո',
'չ' => 'Չ',
'պ' => 'Պ',
'ջ' => 'Ջ',
'ռ' => 'Ռ',
'ս' => 'Ս',
'վ' => 'Վ',
'տ' => 'Տ',
'ր' => 'Ր',
'ց' => 'Ց',
'ւ' => 'Ւ',
'փ' => 'Փ',
'ք' => 'Ք',
'օ' => 'Օ',
'ֆ' => 'Ֆ',
'ა' => 'Ა',
'ბ' => 'Ბ',
'გ' => 'Გ',
'დ' => 'Დ',
'ე' => 'Ე',
'ვ' => 'Ვ',
'ზ' => 'Ზ',
'თ' => 'Თ',
'ი' => 'Ი',
'კ' => 'Კ',
'ლ' => 'Ლ',
'მ' => 'Მ',
'ნ' => 'Ნ',
'ო' => 'Ო',
'პ' => 'Პ',
'ჟ' => 'Ჟ',
'რ' => 'Რ',
'ს' => 'Ს',
'ტ' => 'Ტ',
'უ' => 'Უ',
'ფ' => 'Ფ',
'ქ' => 'Ქ',
'ღ' => 'Ღ',
'' => 'Ყ',
'შ' => 'Შ',
'ჩ' => 'Ჩ',
'ც' => 'Ც',
'ძ' => 'Ძ',
'წ' => 'Წ',
'ჭ' => 'Ჭ',
'ხ' => 'Ხ',
'ჯ' => 'Ჯ',
'ჰ' => 'Ჰ',
'ჱ' => 'Ჱ',
'ჲ' => 'Ჲ',
'ჳ' => 'Ჳ',
'ჴ' => 'Ჴ',
'ჵ' => 'Ჵ',
'ჶ' => 'Ჶ',
'ჷ' => 'Ჷ',
'ჸ' => 'Ჸ',
'ჹ' => 'Ჹ',
'ჺ' => 'Ჺ',
'ჽ' => 'Ჽ',
'ჾ' => 'Ჾ',
'' => 'Ჿ',
'ᏸ' => 'Ᏸ',
'ᏹ' => 'Ᏹ',
'ᏺ' => 'Ᏺ',
'ᏻ' => '',
'ᏼ' => '',
'ᏽ' => 'Ᏽ',
'ᲀ' => 'В',
'ᲁ' => 'Д',
'ᲂ' => 'О',
'ᲃ' => 'С',
'ᲄ' => 'Т',
'ᲅ' => 'Т',
'ᲆ' => 'Ъ',
'ᲇ' => 'Ѣ',
'ᲈ' => 'Ꙋ',
'ᵹ' => 'Ᵹ',
'ᵽ' => 'Ᵽ',
'ᶎ' => 'Ᶎ',
'ḁ' => 'Ḁ',
'ḃ' => 'Ḃ',
'ḅ' => 'Ḅ',
'ḇ' => 'Ḇ',
'ḉ' => 'Ḉ',
'ḋ' => 'Ḋ',
'ḍ' => 'Ḍ',
'ḏ' => 'Ḏ',
'ḑ' => 'Ḑ',
'ḓ' => 'Ḓ',
'ḕ' => 'Ḕ',
'ḗ' => 'Ḗ',
'ḙ' => 'Ḙ',
'ḛ' => 'Ḛ',
'ḝ' => 'Ḝ',
'ḟ' => 'Ḟ',
'ḡ' => 'Ḡ',
'ḣ' => 'Ḣ',
'ḥ' => 'Ḥ',
'ḧ' => 'Ḧ',
'ḩ' => 'Ḩ',
'ḫ' => 'Ḫ',
'ḭ' => 'Ḭ',
'ḯ' => 'Ḯ',
'ḱ' => 'Ḱ',
'ḳ' => 'Ḳ',
'ḵ' => 'Ḵ',
'ḷ' => 'Ḷ',
'ḹ' => 'Ḹ',
'ḻ' => 'Ḻ',
'ḽ' => 'Ḽ',
'ḿ' => 'Ḿ',
'ṁ' => 'Ṁ',
'ṃ' => 'Ṃ',
'ṅ' => 'Ṅ',
'ṇ' => 'Ṇ',
'ṉ' => 'Ṉ',
'ṋ' => 'Ṋ',
'ṍ' => 'Ṍ',
'ṏ' => 'Ṏ',
'ṑ' => 'Ṑ',
'ṓ' => 'Ṓ',
'ṕ' => 'Ṕ',
'ṗ' => 'Ṗ',
'ṙ' => 'Ṙ',
'ṛ' => 'Ṛ',
'ṝ' => 'Ṝ',
'ṟ' => 'Ṟ',
'ṡ' => 'Ṡ',
'ṣ' => 'Ṣ',
'ṥ' => 'Ṥ',
'ṧ' => 'Ṧ',
'ṩ' => 'Ṩ',
'ṫ' => 'Ṫ',
'ṭ' => 'Ṭ',
'ṯ' => 'Ṯ',
'ṱ' => 'Ṱ',
'ṳ' => 'Ṳ',
'ṵ' => 'Ṵ',
'ṷ' => 'Ṷ',
'ṹ' => 'Ṹ',
'ṻ' => 'Ṻ',
'ṽ' => 'Ṽ',
'ṿ' => 'Ṿ',
'ẁ' => 'Ẁ',
'ẃ' => 'Ẃ',
'ẅ' => 'Ẅ',
'ẇ' => 'Ẇ',
'ẉ' => 'Ẉ',
'ẋ' => 'Ẋ',
'ẍ' => 'Ẍ',
'ẏ' => 'Ẏ',
'ẑ' => 'Ẑ',
'ẓ' => 'Ẓ',
'ẕ' => 'Ẕ',
'ẛ' => 'Ṡ',
'ạ' => 'Ạ',
'ả' => 'Ả',
'ấ' => 'Ấ',
'ầ' => 'Ầ',
'ẩ' => 'Ẩ',
'ẫ' => 'Ẫ',
'ậ' => 'Ậ',
'ắ' => 'Ắ',
'ằ' => 'Ằ',
'ẳ' => 'Ẳ',
'ẵ' => 'Ẵ',
'ặ' => 'Ặ',
'ẹ' => 'Ẹ',
'ẻ' => 'Ẻ',
'ẽ' => 'Ẽ',
'ế' => 'Ế',
'ề' => 'Ề',
'ể' => 'Ể',
'ễ' => 'Ễ',
'ệ' => 'Ệ',
'ỉ' => 'Ỉ',
'ị' => 'Ị',
'ọ' => 'Ọ',
'ỏ' => 'Ỏ',
'ố' => 'Ố',
'ồ' => 'Ồ',
'ổ' => 'Ổ',
'ỗ' => 'Ỗ',
'ộ' => 'Ộ',
'ớ' => 'Ớ',
'ờ' => 'Ờ',
'ở' => 'Ở',
'ỡ' => 'Ỡ',
'ợ' => 'Ợ',
'ụ' => 'Ụ',
'ủ' => 'Ủ',
'ứ' => 'Ứ',
'ừ' => 'Ừ',
'ử' => 'Ử',
'ữ' => 'Ữ',
'ự' => 'Ự',
'ỳ' => 'Ỳ',
'ỵ' => 'Ỵ',
'ỷ' => 'Ỷ',
'ỹ' => 'Ỹ',
'ỻ' => 'Ỻ',
'ỽ' => 'Ỽ',
'ỿ' => 'Ỿ',
'ἀ' => 'Ἀ',
'ἁ' => 'Ἁ',
'ἂ' => 'Ἂ',
'ἃ' => 'Ἃ',
'ἄ' => 'Ἄ',
'ἅ' => 'Ἅ',
'ἆ' => 'Ἆ',
'ἇ' => 'Ἇ',
'ἐ' => 'Ἐ',
'ἑ' => 'Ἑ',
'ἒ' => 'Ἒ',
'ἓ' => 'Ἓ',
'ἔ' => 'Ἔ',
'ἕ' => 'Ἕ',
'ἠ' => 'Ἠ',
'ἡ' => 'Ἡ',
'ἢ' => 'Ἢ',
'ἣ' => 'Ἣ',
'ἤ' => 'Ἤ',
'ἥ' => 'Ἥ',
'ἦ' => 'Ἦ',
'ἧ' => 'Ἧ',
'ἰ' => 'Ἰ',
'ἱ' => 'Ἱ',
'ἲ' => 'Ἲ',
'ἳ' => 'Ἳ',
'ἴ' => 'Ἴ',
'ἵ' => 'Ἵ',
'ἶ' => 'Ἶ',
'ἷ' => 'Ἷ',
'ὀ' => 'Ὀ',
'ὁ' => 'Ὁ',
'ὂ' => 'Ὂ',
'ὃ' => 'Ὃ',
'ὄ' => 'Ὄ',
'ὅ' => 'Ὅ',
'ὑ' => 'Ὑ',
'ὓ' => 'Ὓ',
'ὕ' => 'Ὕ',
'ὗ' => 'Ὗ',
'ὠ' => 'Ὠ',
'ὡ' => 'Ὡ',
'ὢ' => 'Ὢ',
'ὣ' => 'Ὣ',
'ὤ' => 'Ὤ',
'ὥ' => 'Ὥ',
'ὦ' => 'Ὦ',
'ὧ' => 'Ὧ',
'ὰ' => 'Ὰ',
'ά' => 'Ά',
'ὲ' => 'Ὲ',
'έ' => 'Έ',
'ὴ' => 'Ὴ',
'ή' => 'Ή',
'ὶ' => 'Ὶ',
'ί' => 'Ί',
'ὸ' => 'Ὸ',
'ό' => 'Ό',
'ὺ' => 'Ὺ',
'ύ' => 'Ύ',
'ὼ' => 'Ὼ',
'ώ' => 'Ώ',
'ᾀ' => 'ᾈ',
'ᾁ' => 'ᾉ',
'ᾂ' => 'ᾊ',
'ᾃ' => 'ᾋ',
'ᾄ' => 'ᾌ',
'ᾅ' => 'ᾍ',
'ᾆ' => 'ᾎ',
'ᾇ' => 'ᾏ',
'ᾐ' => 'ᾘ',
'ᾑ' => 'ᾙ',
'ᾒ' => 'ᾚ',
'ᾓ' => 'ᾛ',
'ᾔ' => 'ᾜ',
'ᾕ' => 'ᾝ',
'ᾖ' => 'ᾞ',
'ᾗ' => 'ᾟ',
'ᾠ' => 'ᾨ',
'ᾡ' => 'ᾩ',
'ᾢ' => 'ᾪ',
'ᾣ' => 'ᾫ',
'ᾤ' => 'ᾬ',
'ᾥ' => 'ᾭ',
'ᾦ' => 'ᾮ',
'ᾧ' => 'ᾯ',
'ᾰ' => 'Ᾰ',
'ᾱ' => 'Ᾱ',
'ᾳ' => 'ᾼ',
'' => 'Ι',
'ῃ' => 'ῌ',
'ῐ' => 'Ῐ',
'ῑ' => 'Ῑ',
'ῠ' => 'Ῠ',
'ῡ' => 'Ῡ',
'ῥ' => 'Ῥ',
'ῳ' => 'ῼ',
'ⅎ' => 'Ⅎ',
'' => '',
'ⅱ' => 'Ⅱ',
'ⅲ' => 'Ⅲ',
'ⅳ' => 'Ⅳ',
'' => '',
'ⅵ' => 'Ⅵ',
'ⅶ' => 'Ⅶ',
'ⅷ' => 'Ⅷ',
'ⅸ' => 'Ⅸ',
'' => '',
'ⅺ' => 'Ⅺ',
'ⅻ' => 'Ⅻ',
'' => '',
'' => '',
'' => '',
'ⅿ' => '',
'ↄ' => 'Ↄ',
'ⓐ' => 'Ⓐ',
'ⓑ' => 'Ⓑ',
'ⓒ' => 'Ⓒ',
'ⓓ' => 'Ⓓ',
'ⓔ' => 'Ⓔ',
'ⓕ' => 'Ⓕ',
'ⓖ' => 'Ⓖ',
'ⓗ' => 'Ⓗ',
'ⓘ' => 'Ⓘ',
'ⓙ' => 'Ⓙ',
'ⓚ' => 'Ⓚ',
'ⓛ' => 'Ⓛ',
'ⓜ' => 'Ⓜ',
'ⓝ' => 'Ⓝ',
'ⓞ' => 'Ⓞ',
'ⓟ' => 'Ⓟ',
'ⓠ' => 'Ⓠ',
'ⓡ' => 'Ⓡ',
'ⓢ' => 'Ⓢ',
'ⓣ' => 'Ⓣ',
'ⓤ' => 'Ⓤ',
'ⓥ' => 'Ⓥ',
'ⓦ' => 'Ⓦ',
'ⓧ' => 'Ⓧ',
'ⓨ' => 'Ⓨ',
'ⓩ' => 'Ⓩ',
'ⰰ' => 'Ⰰ',
'ⰱ' => 'Ⰱ',
'ⰲ' => 'Ⰲ',
'ⰳ' => 'Ⰳ',
'ⰴ' => 'Ⰴ',
'ⰵ' => 'Ⰵ',
'ⰶ' => 'Ⰶ',
'ⰷ' => 'Ⰷ',
'ⰸ' => 'Ⰸ',
'ⰹ' => 'Ⰹ',
'ⰺ' => 'Ⰺ',
'ⰻ' => 'Ⰻ',
'ⰼ' => 'Ⰼ',
'ⰽ' => 'Ⰽ',
'ⰾ' => 'Ⰾ',
'ⰿ' => 'Ⰿ',
'ⱀ' => 'Ⱀ',
'ⱁ' => 'Ⱁ',
'ⱂ' => 'Ⱂ',
'ⱃ' => 'Ⱃ',
'ⱄ' => 'Ⱄ',
'ⱅ' => 'Ⱅ',
'ⱆ' => 'Ⱆ',
'ⱇ' => 'Ⱇ',
'ⱈ' => 'Ⱈ',
'ⱉ' => 'Ⱉ',
'ⱊ' => 'Ⱊ',
'ⱋ' => 'Ⱋ',
'ⱌ' => 'Ⱌ',
'ⱍ' => 'Ⱍ',
'ⱎ' => 'Ⱎ',
'ⱏ' => 'Ⱏ',
'ⱐ' => 'Ⱐ',
'ⱑ' => 'Ⱑ',
'ⱒ' => 'Ⱒ',
'ⱓ' => 'Ⱓ',
'ⱔ' => 'Ⱔ',
'ⱕ' => 'Ⱕ',
'ⱖ' => 'Ⱖ',
'ⱗ' => 'Ⱗ',
'ⱘ' => 'Ⱘ',
'ⱙ' => 'Ⱙ',
'ⱚ' => 'Ⱚ',
'ⱛ' => 'Ⱛ',
'ⱜ' => 'Ⱜ',
'ⱝ' => 'Ⱝ',
'ⱞ' => 'Ⱞ',
'ⱡ' => 'Ⱡ',
'ⱥ' => 'Ⱥ',
'ⱦ' => 'Ⱦ',
'ⱨ' => 'Ⱨ',
'ⱪ' => 'Ⱪ',
'ⱬ' => 'Ⱬ',
'ⱳ' => 'Ⱳ',
'ⱶ' => 'Ⱶ',
'ⲁ' => 'Ⲁ',
'ⲃ' => 'Ⲃ',
'' => 'Ⲅ',
'ⲇ' => 'Ⲇ',
'ⲉ' => 'Ⲉ',
'ⲋ' => 'Ⲋ',
'ⲍ' => 'Ⲍ',
'ⲏ' => '',
'ⲑ' => 'Ⲑ',
'ⲓ' => '',
'ⲕ' => '',
'ⲗ' => 'Ⲗ',
'ⲙ' => '',
'ⲛ' => '',
'ⲝ' => 'Ⲝ',
'' => '',
'ⲡ' => 'Ⲡ',
'' => '',
'' => '',
'ⲧ' => '',
'ⲩ' => '',
'ⲫ' => 'Ⲫ',
'ⲭ' => '',
'ⲯ' => 'Ⲯ',
'ⲱ' => 'Ⲱ',
'ⲳ' => 'Ⲳ',
'ⲵ' => 'Ⲵ',
'ⲷ' => 'Ⲷ',
'ⲹ' => 'Ⲹ',
'ⲻ' => '',
'ⲽ' => 'Ⲽ',
'ⲿ' => 'Ⲿ',
'ⳁ' => 'Ⳁ',
'ⳃ' => 'Ⳃ',
'ⳅ' => 'Ⳅ',
'ⳇ' => '',
'ⳉ' => 'Ⳉ',
'ⳋ' => '',
'ⳍ' => '',
'ⳏ' => 'Ⳏ',
'ⳑ' => '',
'ⳓ' => '',
'ⳕ' => 'Ⳕ',
'ⳗ' => 'Ⳗ',
'ⳙ' => 'Ⳙ',
'ⳛ' => 'Ⳛ',
'ⳝ' => 'Ⳝ',
'ⳟ' => 'Ⳟ',
'ⳡ' => 'Ⳡ',
'ⳣ' => 'Ⳣ',
'ⳬ' => 'Ⳬ',
'ⳮ' => 'Ⳮ',
'ⳳ' => 'Ⳳ',
'ⴀ' => 'Ⴀ',
'ⴁ' => 'Ⴁ',
'ⴂ' => 'Ⴂ',
'ⴃ' => 'Ⴃ',
'ⴄ' => 'Ⴄ',
'ⴅ' => 'Ⴅ',
'ⴆ' => 'Ⴆ',
'ⴇ' => 'Ⴇ',
'ⴈ' => 'Ⴈ',
'ⴉ' => 'Ⴉ',
'ⴊ' => 'Ⴊ',
'ⴋ' => 'Ⴋ',
'ⴌ' => 'Ⴌ',
'ⴍ' => 'Ⴍ',
'ⴎ' => 'Ⴎ',
'ⴏ' => 'Ⴏ',
'ⴐ' => 'Ⴐ',
'ⴑ' => 'Ⴑ',
'ⴒ' => 'Ⴒ',
'ⴓ' => 'Ⴓ',
'ⴔ' => 'Ⴔ',
'ⴕ' => 'Ⴕ',
'ⴖ' => 'Ⴖ',
'ⴗ' => 'Ⴗ',
'ⴘ' => 'Ⴘ',
'ⴙ' => 'Ⴙ',
'ⴚ' => 'Ⴚ',
'ⴛ' => 'Ⴛ',
'ⴜ' => 'Ⴜ',
'ⴝ' => 'Ⴝ',
'ⴞ' => 'Ⴞ',
'ⴟ' => 'Ⴟ',
'ⴠ' => 'Ⴠ',
'ⴡ' => 'Ⴡ',
'ⴢ' => 'Ⴢ',
'ⴣ' => 'Ⴣ',
'ⴤ' => 'Ⴤ',
'ⴥ' => 'Ⴥ',
'ⴧ' => 'Ⴧ',
'ⴭ' => 'Ⴭ',
'ꙁ' => 'Ꙁ',
'ꙃ' => 'Ꙃ',
'ꙅ' => '',
'' => 'Ꙇ',
'ꙉ' => 'Ꙉ',
'ꙋ' => 'Ꙋ',
'ꙍ' => 'Ꙍ',
'ꙏ' => 'Ꙏ',
'ꙑ' => 'Ꙑ',
'ꙓ' => 'Ꙓ',
'ꙕ' => 'Ꙕ',
'ꙗ' => 'Ꙗ',
'ꙙ' => 'Ꙙ',
'ꙛ' => 'Ꙛ',
'ꙝ' => 'Ꙝ',
'ꙟ' => 'Ꙟ',
'ꙡ' => 'Ꙡ',
'ꙣ' => 'Ꙣ',
'ꙥ' => 'Ꙥ',
'ꙧ' => 'Ꙧ',
'ꙩ' => 'Ꙩ',
'ꙫ' => 'Ꙫ',
'ꙭ' => 'Ꙭ',
'ꚁ' => 'Ꚁ',
'ꚃ' => 'Ꚃ',
'ꚅ' => 'Ꚅ',
'ꚇ' => 'Ꚇ',
'ꚉ' => 'Ꚉ',
'ꚋ' => 'Ꚋ',
'ꚍ' => 'Ꚍ',
'ꚏ' => 'Ꚏ',
'ꚑ' => 'Ꚑ',
'ꚓ' => 'Ꚓ',
'ꚕ' => 'Ꚕ',
'ꚗ' => 'Ꚗ',
'ꚙ' => 'Ꚙ',
'ꚛ' => 'Ꚛ',
'ꜣ' => 'Ꜣ',
'ꜥ' => 'Ꜥ',
'ꜧ' => 'Ꜧ',
'ꜩ' => 'Ꜩ',
'ꜫ' => 'Ꜫ',
'ꜭ' => 'Ꜭ',
'ꜯ' => 'Ꜯ',
'ꜳ' => 'Ꜳ',
'ꜵ' => 'Ꜵ',
'ꜷ' => 'Ꜷ',
'ꜹ' => 'Ꜹ',
'ꜻ' => 'Ꜻ',
'ꜽ' => 'Ꜽ',
'ꜿ' => 'Ꜿ',
'ꝁ' => 'Ꝁ',
'ꝃ' => 'Ꝃ',
'ꝅ' => 'Ꝅ',
'ꝇ' => 'Ꝇ',
'ꝉ' => 'Ꝉ',
'ꝋ' => 'Ꝋ',
'ꝍ' => 'Ꝍ',
'ꝏ' => 'Ꝏ',
'ꝑ' => 'Ꝑ',
'ꝓ' => 'Ꝓ',
'ꝕ' => 'Ꝕ',
'ꝗ' => 'Ꝗ',
'ꝙ' => 'Ꝙ',
'ꝛ' => '',
'ꝝ' => 'Ꝝ',
'ꝟ' => 'Ꝟ',
'ꝡ' => 'Ꝡ',
'ꝣ' => 'Ꝣ',
'ꝥ' => 'Ꝥ',
'ꝧ' => 'Ꝧ',
'ꝩ' => 'Ꝩ',
'ꝫ' => '',
'ꝭ' => 'Ꝭ',
'ꝯ' => '',
'ꝺ' => 'Ꝺ',
'ꝼ' => 'Ꝼ',
'ꝿ' => 'Ꝿ',
'ꞁ' => 'Ꞁ',
'ꞃ' => 'Ꞃ',
'ꞅ' => 'Ꞅ',
'ꞇ' => 'Ꞇ',
'' => 'Ꞌ',
'ꞑ' => 'Ꞑ',
'ꞓ' => 'Ꞓ',
'ꞔ' => 'Ꞔ',
'ꞗ' => 'Ꞗ',
'' => '',
'ꞛ' => 'Ꞛ',
'ꞝ' => 'Ꞝ',
'' => 'Ꞟ',
'ꞡ' => 'Ꞡ',
'ꞣ' => 'Ꞣ',
'ꞥ' => 'Ꞥ',
'ꞧ' => 'Ꞧ',
'ꞩ' => 'Ꞩ',
'ꞵ' => '',
'ꞷ' => 'Ꞷ',
'ꞹ' => 'Ꞹ',
'ꞻ' => 'Ꞻ',
'ꞽ' => 'Ꞽ',
'ꞿ' => 'Ꞿ',
'ꟃ' => 'Ꟃ',
'ꟈ' => 'Ꟈ',
'ꟊ' => 'Ꟊ',
'ꟶ' => 'Ꟶ',
'ꭓ' => '',
'ꭰ' => '',
'ꭱ' => '',
'ꭲ' => '',
'ꭳ' => 'Ꭳ',
'ꭴ' => 'Ꭴ',
'' => '',
'ꭶ' => 'Ꭶ',
'ꭷ' => 'Ꭷ',
'ꭸ' => 'Ꭸ',
'ꭹ' => '',
'ꭺ' => '',
'ꭻ' => '',
'ꭼ' => '',
'ꭽ' => 'Ꭽ',
'ꭾ' => '',
'ꭿ' => 'Ꭿ',
'ꮀ' => 'Ꮀ',
'' => 'Ꮁ',
'ꮂ' => 'Ꮂ',
'' => '',
'ꮄ' => 'Ꮄ',
'ꮅ' => 'Ꮅ',
'ꮆ' => 'Ꮆ',
'ꮇ' => '',
'ꮈ' => 'Ꮈ',
'ꮉ' => 'Ꮉ',
'ꮊ' => 'Ꮊ',
'ꮋ' => '',
'ꮌ' => 'Ꮌ',
'ꮍ' => '',
'ꮎ' => 'Ꮎ',
'ꮏ' => 'Ꮏ',
'ꮐ' => '',
'ꮑ' => 'Ꮑ',
'ꮒ' => '',
'' => '',
'ꮔ' => 'Ꮔ',
'ꮕ' => 'Ꮕ',
'ꮖ' => 'Ꮖ',
'ꮗ' => 'Ꮗ',
'ꮘ' => 'Ꮘ',
'ꮙ' => 'Ꮙ',
'ꮚ' => 'Ꮚ',
'ꮛ' => 'Ꮛ',
'ꮜ' => 'Ꮜ',
'ꮝ' => 'Ꮝ',
'ꮞ' => '',
'ꮟ' => '',
'ꮠ' => 'Ꮠ',
'ꮡ' => 'Ꮡ',
'ꮢ' => '',
'ꮣ' => 'Ꮣ',
'ꮤ' => '',
'ꮥ' => '',
'ꮦ' => 'Ꮦ',
'ꮧ' => 'Ꮧ',
'ꮨ' => 'Ꮨ',
'' => '',
'' => '',
'ꮫ' => 'Ꮫ',
'ꮬ' => 'Ꮬ',
'ꮭ' => 'Ꮭ',
'ꮮ' => '',
'' => '',
'ꮰ' => 'Ꮰ',
'ꮱ' => 'Ꮱ',
'ꮲ' => '',
'ꮳ' => 'Ꮳ',
'ꮴ' => 'Ꮴ',
'ꮵ' => 'Ꮵ',
'ꮶ' => '',
'ꮷ' => '',
'ꮸ' => 'Ꮸ',
'ꮹ' => 'Ꮹ',
'ꮺ' => 'Ꮺ',
'ꮻ' => 'Ꮻ',
'ꮼ' => 'Ꮼ',
'ꮽ' => 'Ꮽ',
'ꮾ' => '',
'ꮿ' => 'Ꮿ',
'' => '',
'' => '',
'' => '',
'' => '',
'' => '',
'' => '',
'' => '',
'' => '',
'' => '',
'' => '',
'' => '',
'' => '',
'' => '',
'' => '',
'' => '',
'' => '',
'' => '',
'' => '',
'' => '',
'' => '',
'' => '',
'' => '',
'' => '',
'' => '',
'' => '',
'' => '',
'𐐨' => '𐐀',
'𐐩' => '𐐁',
'𐐪' => '𐐂',
'𐐫' => '𐐃',
'𐐬' => '𐐄',
'𐐭' => '𐐅',
'𐐮' => '𐐆',
'𐐯' => '𐐇',
'𐐰' => '𐐈',
'𐐱' => '𐐉',
'𐐲' => '𐐊',
'𐐳' => '𐐋',
'𐐴' => '𐐌',
'𐐵' => '𐐍',
'𐐶' => '𐐎',
'𐐷' => '𐐏',
'𐐸' => '𐐐',
'𐐹' => '𐐑',
'𐐺' => '𐐒',
'𐐻' => '𐐓',
'𐐼' => '𐐔',
'𐐽' => '𐐕',
'𐐾' => '𐐖',
'𐐿' => '𐐗',
'𐑀' => '𐐘',
'𐑁' => '𐐙',
'𐑂' => '𐐚',
'𐑃' => '𐐛',
'𐑄' => '𐐜',
'𐑅' => '𐐝',
'𐑆' => '𐐞',
'𐑇' => '𐐟',
'𐑈' => '𐐠',
'𐑉' => '𐐡',
'𐑊' => '𐐢',
'𐑋' => '𐐣',
'𐑌' => '𐐤',
'𐑍' => '𐐥',
'𐑎' => '𐐦',
'𐑏' => '𐐧',
'𐓘' => '𐒰',
'𐓙' => '𐒱',
'𐓚' => '𐒲',
'𐓛' => '𐒳',
'𐓜' => '𐒴',
'𐓝' => '𐒵',
'𐓞' => '𐒶',
'𐓟' => '𐒷',
'𐓠' => '𐒸',
'𐓡' => '𐒹',
'𐓢' => '𐒺',
'𐓣' => '𐒻',
'𐓤' => '𐒼',
'𐓥' => '𐒽',
'𐓦' => '𐒾',
'𐓧' => '𐒿',
'𐓨' => '𐓀',
'𐓩' => '𐓁',
'𐓪' => '𐓂',
'𐓫' => '𐓃',
'𐓬' => '𐓄',
'𐓭' => '𐓅',
'𐓮' => '𐓆',
'𐓯' => '𐓇',
'𐓰' => '𐓈',
'𐓱' => '𐓉',
'𐓲' => '𐓊',
'𐓳' => '𐓋',
'𐓴' => '𐓌',
'𐓵' => '𐓍',
'𐓶' => '𐓎',
'𐓷' => '𐓏',
'𐓸' => '𐓐',
'𐓹' => '𐓑',
'𐓺' => '𐓒',
'𐓻' => '𐓓',
'𐳀' => '𐲀',
'𐳁' => '𐲁',
'𐳂' => '𐲂',
'𐳃' => '𐲃',
'𐳄' => '𐲄',
'𐳅' => '𐲅',
'𐳆' => '𐲆',
'𐳇' => '𐲇',
'𐳈' => '𐲈',
'𐳉' => '𐲉',
'𐳊' => '𐲊',
'𐳋' => '𐲋',
'𐳌' => '𐲌',
'𐳍' => '𐲍',
'𐳎' => '𐲎',
'𐳏' => '𐲏',
'𐳐' => '𐲐',
'𐳑' => '𐲑',
'𐳒' => '𐲒',
'𐳓' => '𐲓',
'𐳔' => '𐲔',
'𐳕' => '𐲕',
'𐳖' => '𐲖',
'𐳗' => '𐲗',
'𐳘' => '𐲘',
'𐳙' => '𐲙',
'𐳚' => '𐲚',
'𐳛' => '𐲛',
'𐳜' => '𐲜',
'𐳝' => '𐲝',
'𐳞' => '𐲞',
'𐳟' => '𐲟',
'𐳠' => '𐲠',
'𐳡' => '𐲡',
'𐳢' => '𐲢',
'𐳣' => '𐲣',
'𐳤' => '𐲤',
'𐳥' => '𐲥',
'𐳦' => '𐲦',
'𐳧' => '𐲧',
'𐳨' => '𐲨',
'𐳩' => '𐲩',
'𐳪' => '𐲪',
'𐳫' => '𐲫',
'𐳬' => '𐲬',
'𐳭' => '𐲭',
'𐳮' => '𐲮',
'𐳯' => '𐲯',
'𐳰' => '𐲰',
'𐳱' => '𐲱',
'𐳲' => '𐲲',
'𑣀' => '𑢠',
'𑣁' => '𑢡',
'𑣂' => '𑢢',
'𑣃' => '𑢣',
'𑣄' => '𑢤',
'𑣅' => '𑢥',
'𑣆' => '𑢦',
'𑣇' => '𑢧',
'𑣈' => '𑢨',
'𑣉' => '𑢩',
'𑣊' => '𑢪',
'𑣋' => '𑢫',
'𑣌' => '𑢬',
'𑣍' => '𑢭',
'𑣎' => '𑢮',
'𑣏' => '𑢯',
'𑣐' => '𑢰',
'𑣑' => '𑢱',
'𑣒' => '𑢲',
'𑣓' => '𑢳',
'𑣔' => '𑢴',
'𑣕' => '𑢵',
'𑣖' => '𑢶',
'𑣗' => '𑢷',
'𑣘' => '𑢸',
'𑣙' => '𑢹',
'𑣚' => '𑢺',
'𑣛' => '𑢻',
'𑣜' => '𑢼',
'𑣝' => '𑢽',
'𑣞' => '𑢾',
'𑣟' => '𑢿',
'𖹠' => '𖹀',
'𖹡' => '𖹁',
'𖹢' => '𖹂',
'𖹣' => '𖹃',
'𖹤' => '𖹄',
'𖹥' => '𖹅',
'𖹦' => '𖹆',
'𖹧' => '𖹇',
'𖹨' => '𖹈',
'𖹩' => '𖹉',
'𖹪' => '𖹊',
'𖹫' => '𖹋',
'𖹬' => '𖹌',
'𖹭' => '𖹍',
'𖹮' => '𖹎',
'𖹯' => '𖹏',
'𖹰' => '𖹐',
'𖹱' => '𖹑',
'𖹲' => '𖹒',
'𖹳' => '𖹓',
'𖹴' => '𖹔',
'𖹵' => '𖹕',
'𖹶' => '𖹖',
'𖹷' => '𖹗',
'𖹸' => '𖹘',
'𖹹' => '𖹙',
'𖹺' => '𖹚',
'𖹻' => '𖹛',
'𖹼' => '𖹜',
'𖹽' => '𖹝',
'𖹾' => '𖹞',
'𖹿' => '𖹟',
'𞤢' => '𞤀',
'𞤣' => '𞤁',
'𞤤' => '𞤂',
'𞤥' => '𞤃',
'𞤦' => '𞤄',
'𞤧' => '𞤅',
'𞤨' => '𞤆',
'𞤩' => '𞤇',
'𞤪' => '𞤈',
'𞤫' => '𞤉',
'𞤬' => '𞤊',
'𞤭' => '𞤋',
'𞤮' => '𞤌',
'𞤯' => '𞤍',
'𞤰' => '𞤎',
'𞤱' => '𞤏',
'𞤲' => '𞤐',
'𞤳' => '𞤑',
'𞤴' => '𞤒',
'𞤵' => '𞤓',
'𞤶' => '𞤔',
'𞤷' => '𞤕',
'𞤸' => '𞤖',
'𞤹' => '𞤗',
'𞤺' => '𞤘',
'𞤻' => '𞤙',
'𞤼' => '𞤚',
'𞤽' => '𞤛',
'𞤾' => '𞤜',
'𞤿' => '𞤝',
'𞥀' => '𞤞',
'𞥁' => '𞤟',
'𞥂' => '𞤠',
'𞥃' => '𞤡',
);
<?php
return array (
'A' => 'a',
'B' => 'b',
'C' => 'c',
'D' => 'd',
'E' => 'e',
'F' => 'f',
'G' => 'g',
'H' => 'h',
'I' => 'i',
'J' => 'j',
'K' => 'k',
'L' => 'l',
'M' => 'm',
'N' => 'n',
'O' => 'o',
'P' => 'p',
'Q' => 'q',
'R' => 'r',
'S' => 's',
'T' => 't',
'U' => 'u',
'V' => 'v',
'W' => 'w',
'X' => 'x',
'Y' => 'y',
'Z' => 'z',
'À' => 'à',
'Á' => 'á',
'Â' => 'â',
'Ã' => 'ã',
'Ä' => 'ä',
'Å' => 'å',
'Æ' => 'æ',
'Ç' => 'ç',
'È' => 'è',
'É' => 'é',
'Ê' => 'ê',
'Ë' => 'ë',
'Ì' => 'ì',
'Í' => 'í',
'Î' => 'î',
'Ï' => 'ï',
'Ð' => 'ð',
'Ñ' => 'ñ',
'Ò' => 'ò',
'Ó' => 'ó',
'Ô' => 'ô',
'Õ' => 'õ',
'Ö' => 'ö',
'Ø' => 'ø',
'Ù' => 'ù',
'Ú' => 'ú',
'Û' => 'û',
'Ü' => 'ü',
'Ý' => 'ý',
'Þ' => 'þ',
'Ā' => 'ā',
'Ă' => 'ă',
'Ą' => 'ą',
'Ć' => 'ć',
'Ĉ' => 'ĉ',
'Ċ' => 'ċ',
'Č' => 'č',
'Ď' => 'ď',
'Đ' => 'đ',
'Ē' => 'ē',
'Ĕ' => 'ĕ',
'Ė' => 'ė',
'Ę' => 'ę',
'Ě' => 'ě',
'Ĝ' => 'ĝ',
'Ğ' => 'ğ',
'Ġ' => 'ġ',
'Ģ' => 'ģ',
'Ĥ' => 'ĥ',
'Ħ' => 'ħ',
'Ĩ' => 'ĩ',
'Ī' => 'ī',
'Ĭ' => 'ĭ',
'Į' => 'į',
'İ' => 'i',
'IJ' => 'ij',
'Ĵ' => 'ĵ',
'Ķ' => 'ķ',
'Ĺ' => 'ĺ',
'Ļ' => 'ļ',
'Ľ' => 'ľ',
'Ŀ' => 'ŀ',
'Ł' => 'ł',
'Ń' => 'ń',
'Ņ' => 'ņ',
'Ň' => 'ň',
'Ŋ' => 'ŋ',
'Ō' => 'ō',
'Ŏ' => 'ŏ',
'Ő' => 'ő',
'Œ' => 'œ',
'Ŕ' => 'ŕ',
'Ŗ' => 'ŗ',
'Ř' => 'ř',
'Ś' => 'ś',
'Ŝ' => 'ŝ',
'Ş' => 'ş',
'Š' => 'š',
'Ţ' => 'ţ',
'Ť' => 'ť',
'Ŧ' => 'ŧ',
'Ũ' => 'ũ',
'Ū' => 'ū',
'Ŭ' => 'ŭ',
'Ů' => 'ů',
'Ű' => 'ű',
'Ų' => 'ų',
'Ŵ' => 'ŵ',
'Ŷ' => 'ŷ',
'Ÿ' => 'ÿ',
'Ź' => 'ź',
'Ż' => 'ż',
'Ž' => 'ž',
'Ɓ' => 'ɓ',
'Ƃ' => 'ƃ',
'Ƅ' => 'ƅ',
'Ɔ' => 'ɔ',
'Ƈ' => 'ƈ',
'Ɖ' => 'ɖ',
'Ɗ' => 'ɗ',
'Ƌ' => 'ƌ',
'Ǝ' => 'ǝ',
'Ə' => 'ə',
'Ɛ' => 'ɛ',
'Ƒ' => 'ƒ',
'Ɠ' => 'ɠ',
'Ɣ' => 'ɣ',
'Ɩ' => 'ɩ',
'Ɨ' => 'ɨ',
'Ƙ' => 'ƙ',
'Ɯ' => 'ɯ',
'Ɲ' => 'ɲ',
'Ɵ' => 'ɵ',
'Ơ' => 'ơ',
'Ƣ' => 'ƣ',
'Ƥ' => 'ƥ',
'Ʀ' => 'ʀ',
'Ƨ' => 'ƨ',
'Ʃ' => 'ʃ',
'Ƭ' => 'ƭ',
'Ʈ' => 'ʈ',
'Ư' => 'ư',
'Ʊ' => 'ʊ',
'Ʋ' => 'ʋ',
'Ƴ' => 'ƴ',
'Ƶ' => 'ƶ',
'Ʒ' => 'ʒ',
'Ƹ' => 'ƹ',
'Ƽ' => 'ƽ',
'DŽ' => 'dž',
'Dž' => 'dž',
'LJ' => 'lj',
'Lj' => 'lj',
'NJ' => 'nj',
'Nj' => 'nj',
'Ǎ' => 'ǎ',
'Ǐ' => 'ǐ',
'Ǒ' => 'ǒ',
'Ǔ' => 'ǔ',
'Ǖ' => 'ǖ',
'Ǘ' => 'ǘ',
'Ǚ' => 'ǚ',
'Ǜ' => 'ǜ',
'Ǟ' => 'ǟ',
'Ǡ' => 'ǡ',
'Ǣ' => 'ǣ',
'Ǥ' => 'ǥ',
'Ǧ' => 'ǧ',
'Ǩ' => 'ǩ',
'Ǫ' => 'ǫ',
'Ǭ' => 'ǭ',
'Ǯ' => 'ǯ',
'DZ' => 'dz',
'Dz' => 'dz',
'Ǵ' => 'ǵ',
'Ƕ' => 'ƕ',
'Ƿ' => 'ƿ',
'Ǹ' => 'ǹ',
'Ǻ' => 'ǻ',
'Ǽ' => 'ǽ',
'Ǿ' => 'ǿ',
'Ȁ' => 'ȁ',
'Ȃ' => 'ȃ',
'Ȅ' => 'ȅ',
'Ȇ' => 'ȇ',
'Ȉ' => 'ȉ',
'Ȋ' => 'ȋ',
'Ȍ' => 'ȍ',
'Ȏ' => 'ȏ',
'Ȑ' => 'ȑ',
'Ȓ' => 'ȓ',
'Ȕ' => 'ȕ',
'Ȗ' => 'ȗ',
'Ș' => 'ș',
'Ț' => 'ț',
'Ȝ' => 'ȝ',
'Ȟ' => 'ȟ',
'Ƞ' => 'ƞ',
'Ȣ' => 'ȣ',
'Ȥ' => 'ȥ',
'Ȧ' => 'ȧ',
'Ȩ' => 'ȩ',
'Ȫ' => 'ȫ',
'Ȭ' => 'ȭ',
'Ȯ' => 'ȯ',
'Ȱ' => 'ȱ',
'Ȳ' => 'ȳ',
'Ⱥ' => 'ⱥ',
'Ȼ' => 'ȼ',
'Ƚ' => 'ƚ',
'Ⱦ' => 'ⱦ',
'Ɂ' => 'ɂ',
'Ƀ' => 'ƀ',
'Ʉ' => 'ʉ',
'Ʌ' => 'ʌ',
'Ɇ' => 'ɇ',
'Ɉ' => 'ɉ',
'Ɋ' => 'ɋ',
'Ɍ' => 'ɍ',
'Ɏ' => 'ɏ',
'Ͱ' => 'ͱ',
'Ͳ' => 'ͳ',
'Ͷ' => 'ͷ',
'Ϳ' => 'ϳ',
'Ά' => 'ά',
'Έ' => 'έ',
'Ή' => 'ή',
'Ί' => 'ί',
'Ό' => 'ό',
'Ύ' => 'ύ',
'Ώ' => 'ώ',
'Α' => 'α',
'Β' => 'β',
'Γ' => 'γ',
'Δ' => 'δ',
'Ε' => 'ε',
'Ζ' => 'ζ',
'Η' => 'η',
'Θ' => 'θ',
'Ι' => 'ι',
'Κ' => 'κ',
'Λ' => 'λ',
'Μ' => 'μ',
'Ν' => 'ν',
'Ξ' => 'ξ',
'Ο' => 'ο',
'Π' => 'π',
'Ρ' => 'ρ',
'Σ' => 'σ',
'Τ' => 'τ',
'Υ' => 'υ',
'Φ' => 'φ',
'Χ' => 'χ',
'Ψ' => 'ψ',
'Ω' => 'ω',
'Ϊ' => 'ϊ',
'Ϋ' => 'ϋ',
'Ϗ' => 'ϗ',
'Ϙ' => 'ϙ',
'Ϛ' => 'ϛ',
'Ϝ' => 'ϝ',
'Ϟ' => 'ϟ',
'Ϡ' => 'ϡ',
'Ϣ' => 'ϣ',
'Ϥ' => 'ϥ',
'Ϧ' => 'ϧ',
'Ϩ' => 'ϩ',
'Ϫ' => 'ϫ',
'Ϭ' => 'ϭ',
'Ϯ' => 'ϯ',
'ϴ' => 'θ',
'Ϸ' => 'ϸ',
'Ϲ' => 'ϲ',
'Ϻ' => 'ϻ',
'Ͻ' => 'ͻ',
'Ͼ' => 'ͼ',
'Ͽ' => 'ͽ',
'Ѐ' => 'ѐ',
'Ё' => 'ё',
'Ђ' => 'ђ',
'Ѓ' => 'ѓ',
'Є' => 'є',
'Ѕ' => 'ѕ',
'І' => 'і',
'Ї' => 'ї',
'Ј' => 'ј',
'Љ' => 'љ',
'Њ' => 'њ',
'Ћ' => 'ћ',
'Ќ' => 'ќ',
'Ѝ' => 'ѝ',
'Ў' => 'ў',
'Џ' => 'џ',
'А' => 'а',
'Б' => 'б',
'В' => 'в',
'Г' => 'г',
'Д' => 'д',
'Е' => 'е',
'Ж' => 'ж',
'З' => 'з',
'И' => 'и',
'Й' => 'й',
'К' => 'к',
'Л' => 'л',
'М' => 'м',
'Н' => 'н',
'О' => 'о',
'П' => 'п',
'Р' => 'р',
'С' => 'с',
'Т' => 'т',
'У' => 'у',
'Ф' => 'ф',
'Х' => 'х',
'Ц' => 'ц',
'Ч' => 'ч',
'Ш' => 'ш',
'Щ' => 'щ',
'Ъ' => 'ъ',
'Ы' => 'ы',
'Ь' => 'ь',
'Э' => 'э',
'Ю' => 'ю',
'Я' => 'я',
'Ѡ' => 'ѡ',
'Ѣ' => 'ѣ',
'Ѥ' => 'ѥ',
'Ѧ' => 'ѧ',
'Ѩ' => 'ѩ',
'Ѫ' => 'ѫ',
'Ѭ' => 'ѭ',
'Ѯ' => 'ѯ',
'Ѱ' => 'ѱ',
'Ѳ' => 'ѳ',
'Ѵ' => 'ѵ',
'Ѷ' => 'ѷ',
'Ѹ' => 'ѹ',
'Ѻ' => 'ѻ',
'Ѽ' => 'ѽ',
'Ѿ' => 'ѿ',
'Ҁ' => 'ҁ',
'Ҋ' => 'ҋ',
'Ҍ' => 'ҍ',
'Ҏ' => 'ҏ',
'Ґ' => 'ґ',
'Ғ' => 'ғ',
'Ҕ' => 'ҕ',
'Җ' => 'җ',
'Ҙ' => 'ҙ',
'Қ' => 'қ',
'Ҝ' => 'ҝ',
'Ҟ' => 'ҟ',
'Ҡ' => 'ҡ',
'Ң' => 'ң',
'Ҥ' => 'ҥ',
'Ҧ' => 'ҧ',
'Ҩ' => 'ҩ',
'Ҫ' => 'ҫ',
'Ҭ' => 'ҭ',
'Ү' => 'ү',
'Ұ' => 'ұ',
'Ҳ' => 'ҳ',
'Ҵ' => 'ҵ',
'Ҷ' => 'ҷ',
'Ҹ' => 'ҹ',
'Һ' => 'һ',
'Ҽ' => 'ҽ',
'Ҿ' => 'ҿ',
'Ӏ' => 'ӏ',
'Ӂ' => 'ӂ',
'Ӄ' => 'ӄ',
'Ӆ' => 'ӆ',
'Ӈ' => 'ӈ',
'Ӊ' => 'ӊ',
'Ӌ' => 'ӌ',
'Ӎ' => 'ӎ',
'Ӑ' => 'ӑ',
'Ӓ' => 'ӓ',
'Ӕ' => 'ӕ',
'Ӗ' => 'ӗ',
'Ә' => 'ә',
'Ӛ' => 'ӛ',
'Ӝ' => 'ӝ',
'Ӟ' => 'ӟ',
'Ӡ' => 'ӡ',
'Ӣ' => 'ӣ',
'Ӥ' => 'ӥ',
'Ӧ' => 'ӧ',
'Ө' => 'ө',
'Ӫ' => 'ӫ',
'Ӭ' => 'ӭ',
'Ӯ' => 'ӯ',
'Ӱ' => 'ӱ',
'Ӳ' => 'ӳ',
'Ӵ' => 'ӵ',
'Ӷ' => 'ӷ',
'Ӹ' => 'ӹ',
'Ӻ' => 'ӻ',
'Ӽ' => 'ӽ',
'Ӿ' => 'ӿ',
'Ԁ' => 'ԁ',
'Ԃ' => 'ԃ',
'Ԅ' => 'ԅ',
'Ԇ' => 'ԇ',
'Ԉ' => 'ԉ',
'Ԋ' => 'ԋ',
'Ԍ' => 'ԍ',
'Ԏ' => 'ԏ',
'Ԑ' => 'ԑ',
'Ԓ' => 'ԓ',
'Ԕ' => 'ԕ',
'Ԗ' => 'ԗ',
'Ԙ' => 'ԙ',
'Ԛ' => 'ԛ',
'Ԝ' => 'ԝ',
'Ԟ' => 'ԟ',
'Ԡ' => 'ԡ',
'Ԣ' => 'ԣ',
'Ԥ' => 'ԥ',
'Ԧ' => 'ԧ',
'Ԩ' => 'ԩ',
'Ԫ' => 'ԫ',
'Ԭ' => 'ԭ',
'Ԯ' => 'ԯ',
'Ա' => 'ա',
'Բ' => 'բ',
'Գ' => 'գ',
'Դ' => 'դ',
'Ե' => 'ե',
'Զ' => 'զ',
'Է' => 'է',
'Ը' => 'ը',
'Թ' => 'թ',
'Ժ' => 'ժ',
'Ի' => 'ի',
'Լ' => 'լ',
'Խ' => 'խ',
'Ծ' => 'ծ',
'Կ' => 'կ',
'Հ' => 'հ',
'Ձ' => 'ձ',
'Ղ' => 'ղ',
'Ճ' => 'ճ',
'Մ' => 'մ',
'Յ' => 'յ',
'Ն' => 'ն',
'Շ' => 'շ',
'Ո' => 'ո',
'Չ' => 'չ',
'Պ' => 'պ',
'Ջ' => 'ջ',
'Ռ' => 'ռ',
'Ս' => 'ս',
'Վ' => 'վ',
'Տ' => 'տ',
'Ր' => 'ր',
'Ց' => 'ց',
'Ւ' => 'ւ',
'Փ' => 'փ',
'Ք' => 'ք',
'Օ' => 'օ',
'Ֆ' => 'ֆ',
'Ⴀ' => 'ⴀ',
'Ⴁ' => 'ⴁ',
'Ⴂ' => 'ⴂ',
'Ⴃ' => 'ⴃ',
'Ⴄ' => 'ⴄ',
'Ⴅ' => 'ⴅ',
'Ⴆ' => 'ⴆ',
'Ⴇ' => 'ⴇ',
'Ⴈ' => 'ⴈ',
'Ⴉ' => 'ⴉ',
'Ⴊ' => 'ⴊ',
'Ⴋ' => 'ⴋ',
'Ⴌ' => 'ⴌ',
'Ⴍ' => 'ⴍ',
'Ⴎ' => 'ⴎ',
'Ⴏ' => 'ⴏ',
'Ⴐ' => 'ⴐ',
'Ⴑ' => 'ⴑ',
'Ⴒ' => 'ⴒ',
'Ⴓ' => 'ⴓ',
'Ⴔ' => 'ⴔ',
'Ⴕ' => 'ⴕ',
'Ⴖ' => 'ⴖ',
'Ⴗ' => 'ⴗ',
'Ⴘ' => 'ⴘ',
'Ⴙ' => 'ⴙ',
'Ⴚ' => 'ⴚ',
'Ⴛ' => 'ⴛ',
'Ⴜ' => 'ⴜ',
'Ⴝ' => 'ⴝ',
'Ⴞ' => 'ⴞ',
'Ⴟ' => 'ⴟ',
'Ⴠ' => 'ⴠ',
'Ⴡ' => 'ⴡ',
'Ⴢ' => 'ⴢ',
'Ⴣ' => 'ⴣ',
'Ⴤ' => 'ⴤ',
'Ⴥ' => 'ⴥ',
'Ⴧ' => 'ⴧ',
'Ⴭ' => 'ⴭ',
'' => 'ꭰ',
'' => 'ꭱ',
'' => 'ꭲ',
'Ꭳ' => 'ꭳ',
'Ꭴ' => 'ꭴ',
'' => '',
'Ꭶ' => 'ꭶ',
'Ꭷ' => 'ꭷ',
'Ꭸ' => 'ꭸ',
'' => 'ꭹ',
'' => 'ꭺ',
'' => 'ꭻ',
'' => 'ꭼ',
'Ꭽ' => 'ꭽ',
'' => 'ꭾ',
'Ꭿ' => 'ꭿ',
'Ꮀ' => 'ꮀ',
'Ꮁ' => '',
'Ꮂ' => 'ꮂ',
'' => '',
'Ꮄ' => 'ꮄ',
'Ꮅ' => 'ꮅ',
'Ꮆ' => 'ꮆ',
'' => 'ꮇ',
'Ꮈ' => 'ꮈ',
'Ꮉ' => 'ꮉ',
'Ꮊ' => 'ꮊ',
'' => 'ꮋ',
'Ꮌ' => 'ꮌ',
'' => 'ꮍ',
'Ꮎ' => 'ꮎ',
'Ꮏ' => 'ꮏ',
'' => 'ꮐ',
'Ꮑ' => 'ꮑ',
'' => 'ꮒ',
'' => '',
'Ꮔ' => 'ꮔ',
'Ꮕ' => 'ꮕ',
'Ꮖ' => 'ꮖ',
'Ꮗ' => 'ꮗ',
'Ꮘ' => 'ꮘ',
'Ꮙ' => 'ꮙ',
'Ꮚ' => 'ꮚ',
'Ꮛ' => 'ꮛ',
'Ꮜ' => 'ꮜ',
'Ꮝ' => 'ꮝ',
'' => 'ꮞ',
'' => 'ꮟ',
'Ꮠ' => 'ꮠ',
'Ꮡ' => 'ꮡ',
'' => 'ꮢ',
'Ꮣ' => 'ꮣ',
'' => 'ꮤ',
'' => 'ꮥ',
'Ꮦ' => 'ꮦ',
'Ꮧ' => 'ꮧ',
'Ꮨ' => 'ꮨ',
'' => '',
'' => '',
'Ꮫ' => 'ꮫ',
'Ꮬ' => 'ꮬ',
'Ꮭ' => 'ꮭ',
'' => 'ꮮ',
'' => '',
'Ꮰ' => 'ꮰ',
'Ꮱ' => 'ꮱ',
'' => 'ꮲ',
'Ꮳ' => 'ꮳ',
'Ꮴ' => 'ꮴ',
'Ꮵ' => 'ꮵ',
'' => 'ꮶ',
'' => 'ꮷ',
'Ꮸ' => 'ꮸ',
'Ꮹ' => 'ꮹ',
'Ꮺ' => 'ꮺ',
'Ꮻ' => 'ꮻ',
'Ꮼ' => 'ꮼ',
'Ꮽ' => 'ꮽ',
'' => 'ꮾ',
'Ꮿ' => 'ꮿ',
'Ᏸ' => 'ᏸ',
'Ᏹ' => 'ᏹ',
'Ᏺ' => 'ᏺ',
'' => 'ᏻ',
'' => 'ᏼ',
'Ᏽ' => 'ᏽ',
'Ა' => 'ა',
'Ბ' => 'ბ',
'Გ' => 'გ',
'Დ' => 'დ',
'Ე' => 'ე',
'Ვ' => 'ვ',
'Ზ' => 'ზ',
'Თ' => 'თ',
'Ი' => 'ი',
'Კ' => 'კ',
'Ლ' => 'ლ',
'Მ' => 'მ',
'Ნ' => 'ნ',
'Ო' => 'ო',
'Პ' => 'პ',
'Ჟ' => 'ჟ',
'Რ' => 'რ',
'Ს' => 'ს',
'Ტ' => 'ტ',
'Უ' => 'უ',
'Ფ' => 'ფ',
'Ქ' => 'ქ',
'Ღ' => 'ღ',
'Ყ' => '',
'Შ' => 'შ',
'Ჩ' => 'ჩ',
'Ც' => 'ც',
'Ძ' => 'ძ',
'Წ' => 'წ',
'Ჭ' => 'ჭ',
'Ხ' => 'ხ',
'Ჯ' => 'ჯ',
'Ჰ' => 'ჰ',
'Ჱ' => 'ჱ',
'Ჲ' => 'ჲ',
'Ჳ' => 'ჳ',
'Ჴ' => 'ჴ',
'Ჵ' => 'ჵ',
'Ჶ' => 'ჶ',
'Ჷ' => 'ჷ',
'Ჸ' => 'ჸ',
'Ჹ' => 'ჹ',
'Ჺ' => 'ჺ',
'Ჽ' => 'ჽ',
'Ჾ' => 'ჾ',
'Ჿ' => '',
'Ḁ' => 'ḁ',
'Ḃ' => 'ḃ',
'Ḅ' => 'ḅ',
'Ḇ' => 'ḇ',
'Ḉ' => 'ḉ',
'Ḋ' => 'ḋ',
'Ḍ' => 'ḍ',
'Ḏ' => 'ḏ',
'Ḑ' => 'ḑ',
'Ḓ' => 'ḓ',
'Ḕ' => 'ḕ',
'Ḗ' => 'ḗ',
'Ḙ' => 'ḙ',
'Ḛ' => 'ḛ',
'Ḝ' => 'ḝ',
'Ḟ' => 'ḟ',
'Ḡ' => 'ḡ',
'Ḣ' => 'ḣ',
'Ḥ' => 'ḥ',
'Ḧ' => 'ḧ',
'Ḩ' => 'ḩ',
'Ḫ' => 'ḫ',
'Ḭ' => 'ḭ',
'Ḯ' => 'ḯ',
'Ḱ' => 'ḱ',
'Ḳ' => 'ḳ',
'Ḵ' => 'ḵ',
'Ḷ' => 'ḷ',
'Ḹ' => 'ḹ',
'Ḻ' => 'ḻ',
'Ḽ' => 'ḽ',
'Ḿ' => 'ḿ',
'Ṁ' => 'ṁ',
'Ṃ' => 'ṃ',
'Ṅ' => 'ṅ',
'Ṇ' => 'ṇ',
'Ṉ' => 'ṉ',
'Ṋ' => 'ṋ',
'Ṍ' => 'ṍ',
'Ṏ' => 'ṏ',
'Ṑ' => 'ṑ',
'Ṓ' => 'ṓ',
'Ṕ' => 'ṕ',
'Ṗ' => 'ṗ',
'Ṙ' => 'ṙ',
'Ṛ' => 'ṛ',
'Ṝ' => 'ṝ',
'Ṟ' => 'ṟ',
'Ṡ' => 'ṡ',
'Ṣ' => 'ṣ',
'Ṥ' => 'ṥ',
'Ṧ' => 'ṧ',
'Ṩ' => 'ṩ',
'Ṫ' => 'ṫ',
'Ṭ' => 'ṭ',
'Ṯ' => 'ṯ',
'Ṱ' => 'ṱ',
'Ṳ' => 'ṳ',
'Ṵ' => 'ṵ',
'Ṷ' => 'ṷ',
'Ṹ' => 'ṹ',
'Ṻ' => 'ṻ',
'Ṽ' => 'ṽ',
'Ṿ' => 'ṿ',
'Ẁ' => 'ẁ',
'Ẃ' => 'ẃ',
'Ẅ' => 'ẅ',
'Ẇ' => 'ẇ',
'Ẉ' => 'ẉ',
'Ẋ' => 'ẋ',
'Ẍ' => 'ẍ',
'Ẏ' => 'ẏ',
'Ẑ' => 'ẑ',
'Ẓ' => 'ẓ',
'Ẕ' => 'ẕ',
'ẞ' => 'ß',
'Ạ' => 'ạ',
'Ả' => 'ả',
'Ấ' => 'ấ',
'Ầ' => 'ầ',
'Ẩ' => 'ẩ',
'Ẫ' => 'ẫ',
'Ậ' => 'ậ',
'Ắ' => 'ắ',
'Ằ' => 'ằ',
'Ẳ' => 'ẳ',
'Ẵ' => 'ẵ',
'Ặ' => 'ặ',
'Ẹ' => 'ẹ',
'Ẻ' => 'ẻ',
'Ẽ' => 'ẽ',
'Ế' => 'ế',
'Ề' => 'ề',
'Ể' => 'ể',
'Ễ' => 'ễ',
'Ệ' => 'ệ',
'Ỉ' => 'ỉ',
'Ị' => 'ị',
'Ọ' => 'ọ',
'Ỏ' => 'ỏ',
'Ố' => 'ố',
'Ồ' => 'ồ',
'Ổ' => 'ổ',
'Ỗ' => 'ỗ',
'Ộ' => 'ộ',
'Ớ' => 'ớ',
'Ờ' => 'ờ',
'Ở' => 'ở',
'Ỡ' => 'ỡ',
'Ợ' => 'ợ',
'Ụ' => 'ụ',
'Ủ' => 'ủ',
'Ứ' => 'ứ',
'Ừ' => 'ừ',
'Ử' => 'ử',
'Ữ' => 'ữ',
'Ự' => 'ự',
'Ỳ' => 'ỳ',
'Ỵ' => 'ỵ',
'Ỷ' => 'ỷ',
'Ỹ' => 'ỹ',
'Ỻ' => 'ỻ',
'Ỽ' => 'ỽ',
'Ỿ' => 'ỿ',
'Ἀ' => 'ἀ',
'Ἁ' => 'ἁ',
'Ἂ' => 'ἂ',
'Ἃ' => 'ἃ',
'Ἄ' => 'ἄ',
'Ἅ' => 'ἅ',
'Ἆ' => 'ἆ',
'Ἇ' => 'ἇ',
'Ἐ' => 'ἐ',
'Ἑ' => 'ἑ',
'Ἒ' => 'ἒ',
'Ἓ' => 'ἓ',
'Ἔ' => 'ἔ',
'Ἕ' => 'ἕ',
'Ἠ' => 'ἠ',
'Ἡ' => 'ἡ',
'Ἢ' => 'ἢ',
'Ἣ' => 'ἣ',
'Ἤ' => 'ἤ',
'Ἥ' => 'ἥ',
'Ἦ' => 'ἦ',
'Ἧ' => 'ἧ',
'Ἰ' => 'ἰ',
'Ἱ' => 'ἱ',
'Ἲ' => 'ἲ',
'Ἳ' => 'ἳ',
'Ἴ' => 'ἴ',
'Ἵ' => 'ἵ',
'Ἶ' => 'ἶ',
'Ἷ' => 'ἷ',
'Ὀ' => 'ὀ',
'Ὁ' => 'ὁ',
'Ὂ' => 'ὂ',
'Ὃ' => 'ὃ',
'Ὄ' => 'ὄ',
'Ὅ' => 'ὅ',
'Ὑ' => 'ὑ',
'Ὓ' => 'ὓ',
'Ὕ' => 'ὕ',
'Ὗ' => 'ὗ',
'Ὠ' => 'ὠ',
'Ὡ' => 'ὡ',
'Ὢ' => 'ὢ',
'Ὣ' => 'ὣ',
'Ὤ' => 'ὤ',
'Ὥ' => 'ὥ',
'Ὦ' => 'ὦ',
'Ὧ' => 'ὧ',
'ᾈ' => 'ᾀ',
'ᾉ' => 'ᾁ',
'ᾊ' => 'ᾂ',
'ᾋ' => 'ᾃ',
'ᾌ' => 'ᾄ',
'ᾍ' => 'ᾅ',
'ᾎ' => 'ᾆ',
'ᾏ' => 'ᾇ',
'ᾘ' => 'ᾐ',
'ᾙ' => 'ᾑ',
'ᾚ' => 'ᾒ',
'ᾛ' => 'ᾓ',
'ᾜ' => 'ᾔ',
'ᾝ' => 'ᾕ',
'ᾞ' => 'ᾖ',
'ᾟ' => 'ᾗ',
'ᾨ' => 'ᾠ',
'ᾩ' => 'ᾡ',
'ᾪ' => 'ᾢ',
'ᾫ' => 'ᾣ',
'ᾬ' => 'ᾤ',
'ᾭ' => 'ᾥ',
'ᾮ' => 'ᾦ',
'ᾯ' => 'ᾧ',
'Ᾰ' => 'ᾰ',
'Ᾱ' => 'ᾱ',
'Ὰ' => 'ὰ',
'Ά' => 'ά',
'ᾼ' => 'ᾳ',
'Ὲ' => 'ὲ',
'Έ' => 'έ',
'Ὴ' => 'ὴ',
'Ή' => 'ή',
'ῌ' => 'ῃ',
'Ῐ' => 'ῐ',
'Ῑ' => 'ῑ',
'Ὶ' => 'ὶ',
'Ί' => 'ί',
'Ῠ' => 'ῠ',
'Ῡ' => 'ῡ',
'Ὺ' => 'ὺ',
'Ύ' => 'ύ',
'Ῥ' => 'ῥ',
'Ὸ' => 'ὸ',
'Ό' => 'ό',
'Ὼ' => 'ὼ',
'Ώ' => 'ώ',
'ῼ' => 'ῳ',
'Ω' => 'ω',
'' => 'k',
'Å' => 'å',
'Ⅎ' => 'ⅎ',
'' => '',
'Ⅱ' => 'ⅱ',
'Ⅲ' => 'ⅲ',
'Ⅳ' => 'ⅳ',
'' => '',
'Ⅵ' => 'ⅵ',
'Ⅶ' => 'ⅶ',
'Ⅷ' => 'ⅷ',
'Ⅸ' => 'ⅸ',
'' => '',
'Ⅺ' => 'ⅺ',
'Ⅻ' => 'ⅻ',
'' => '',
'' => '',
'' => '',
'' => 'ⅿ',
'Ↄ' => 'ↄ',
'Ⓐ' => 'ⓐ',
'Ⓑ' => 'ⓑ',
'Ⓒ' => 'ⓒ',
'Ⓓ' => 'ⓓ',
'Ⓔ' => 'ⓔ',
'Ⓕ' => 'ⓕ',
'Ⓖ' => 'ⓖ',
'Ⓗ' => 'ⓗ',
'Ⓘ' => 'ⓘ',
'Ⓙ' => 'ⓙ',
'Ⓚ' => 'ⓚ',
'Ⓛ' => 'ⓛ',
'Ⓜ' => 'ⓜ',
'Ⓝ' => 'ⓝ',
'Ⓞ' => 'ⓞ',
'Ⓟ' => 'ⓟ',
'Ⓠ' => 'ⓠ',
'Ⓡ' => 'ⓡ',
'Ⓢ' => 'ⓢ',
'Ⓣ' => 'ⓣ',
'Ⓤ' => 'ⓤ',
'Ⓥ' => 'ⓥ',
'Ⓦ' => 'ⓦ',
'Ⓧ' => 'ⓧ',
'Ⓨ' => 'ⓨ',
'Ⓩ' => 'ⓩ',
'Ⰰ' => 'ⰰ',
'Ⰱ' => 'ⰱ',
'Ⰲ' => 'ⰲ',
'Ⰳ' => 'ⰳ',
'Ⰴ' => 'ⰴ',
'Ⰵ' => 'ⰵ',
'Ⰶ' => 'ⰶ',
'Ⰷ' => 'ⰷ',
'Ⰸ' => 'ⰸ',
'Ⰹ' => 'ⰹ',
'Ⰺ' => 'ⰺ',
'Ⰻ' => 'ⰻ',
'Ⰼ' => 'ⰼ',
'Ⰽ' => 'ⰽ',
'Ⰾ' => 'ⰾ',
'Ⰿ' => 'ⰿ',
'Ⱀ' => 'ⱀ',
'Ⱁ' => 'ⱁ',
'Ⱂ' => 'ⱂ',
'Ⱃ' => 'ⱃ',
'Ⱄ' => 'ⱄ',
'Ⱅ' => 'ⱅ',
'Ⱆ' => 'ⱆ',
'Ⱇ' => 'ⱇ',
'Ⱈ' => 'ⱈ',
'Ⱉ' => 'ⱉ',
'Ⱊ' => 'ⱊ',
'Ⱋ' => 'ⱋ',
'Ⱌ' => 'ⱌ',
'Ⱍ' => 'ⱍ',
'Ⱎ' => 'ⱎ',
'Ⱏ' => 'ⱏ',
'Ⱐ' => 'ⱐ',
'Ⱑ' => 'ⱑ',
'Ⱒ' => 'ⱒ',
'Ⱓ' => 'ⱓ',
'Ⱔ' => 'ⱔ',
'Ⱕ' => 'ⱕ',
'Ⱖ' => 'ⱖ',
'Ⱗ' => 'ⱗ',
'Ⱘ' => 'ⱘ',
'Ⱙ' => 'ⱙ',
'Ⱚ' => 'ⱚ',
'Ⱛ' => 'ⱛ',
'Ⱜ' => 'ⱜ',
'Ⱝ' => 'ⱝ',
'Ⱞ' => 'ⱞ',
'Ⱡ' => 'ⱡ',
'Ɫ' => 'ɫ',
'Ᵽ' => 'ᵽ',
'Ɽ' => 'ɽ',
'Ⱨ' => 'ⱨ',
'Ⱪ' => 'ⱪ',
'Ⱬ' => 'ⱬ',
'Ɑ' => 'ɑ',
'Ɱ' => 'ɱ',
'Ɐ' => 'ɐ',
'Ɒ' => 'ɒ',
'Ⱳ' => 'ⱳ',
'Ⱶ' => 'ⱶ',
'Ȿ' => 'ȿ',
'Ɀ' => 'ɀ',
'Ⲁ' => 'ⲁ',
'Ⲃ' => 'ⲃ',
'Ⲅ' => '',
'Ⲇ' => 'ⲇ',
'Ⲉ' => 'ⲉ',
'Ⲋ' => 'ⲋ',
'Ⲍ' => 'ⲍ',
'' => 'ⲏ',
'Ⲑ' => 'ⲑ',
'' => 'ⲓ',
'' => 'ⲕ',
'Ⲗ' => 'ⲗ',
'' => 'ⲙ',
'' => 'ⲛ',
'Ⲝ' => 'ⲝ',
'' => '',
'Ⲡ' => 'ⲡ',
'' => '',
'' => '',
'' => 'ⲧ',
'' => 'ⲩ',
'Ⲫ' => 'ⲫ',
'' => 'ⲭ',
'Ⲯ' => 'ⲯ',
'Ⲱ' => 'ⲱ',
'Ⲳ' => 'ⲳ',
'Ⲵ' => 'ⲵ',
'Ⲷ' => 'ⲷ',
'Ⲹ' => 'ⲹ',
'' => 'ⲻ',
'Ⲽ' => 'ⲽ',
'Ⲿ' => 'ⲿ',
'Ⳁ' => 'ⳁ',
'Ⳃ' => 'ⳃ',
'Ⳅ' => 'ⳅ',
'' => 'ⳇ',
'Ⳉ' => 'ⳉ',
'' => 'ⳋ',
'' => 'ⳍ',
'Ⳏ' => 'ⳏ',
'' => 'ⳑ',
'' => 'ⳓ',
'Ⳕ' => 'ⳕ',
'Ⳗ' => 'ⳗ',
'Ⳙ' => 'ⳙ',
'Ⳛ' => 'ⳛ',
'Ⳝ' => 'ⳝ',
'Ⳟ' => 'ⳟ',
'Ⳡ' => 'ⳡ',
'Ⳣ' => 'ⳣ',
'Ⳬ' => 'ⳬ',
'Ⳮ' => 'ⳮ',
'Ⳳ' => 'ⳳ',
'Ꙁ' => 'ꙁ',
'Ꙃ' => 'ꙃ',
'' => 'ꙅ',
'Ꙇ' => '',
'Ꙉ' => 'ꙉ',
'Ꙋ' => 'ꙋ',
'Ꙍ' => 'ꙍ',
'Ꙏ' => 'ꙏ',
'Ꙑ' => 'ꙑ',
'Ꙓ' => 'ꙓ',
'Ꙕ' => 'ꙕ',
'Ꙗ' => 'ꙗ',
'Ꙙ' => 'ꙙ',
'Ꙛ' => 'ꙛ',
'Ꙝ' => 'ꙝ',
'Ꙟ' => 'ꙟ',
'Ꙡ' => 'ꙡ',
'Ꙣ' => 'ꙣ',
'Ꙥ' => 'ꙥ',
'Ꙧ' => 'ꙧ',
'Ꙩ' => 'ꙩ',
'Ꙫ' => 'ꙫ',
'Ꙭ' => 'ꙭ',
'Ꚁ' => 'ꚁ',
'Ꚃ' => 'ꚃ',
'Ꚅ' => 'ꚅ',
'Ꚇ' => 'ꚇ',
'Ꚉ' => 'ꚉ',
'Ꚋ' => 'ꚋ',
'Ꚍ' => 'ꚍ',
'Ꚏ' => 'ꚏ',
'Ꚑ' => 'ꚑ',
'Ꚓ' => 'ꚓ',
'Ꚕ' => 'ꚕ',
'Ꚗ' => 'ꚗ',
'Ꚙ' => 'ꚙ',
'Ꚛ' => 'ꚛ',
'Ꜣ' => 'ꜣ',
'Ꜥ' => 'ꜥ',
'Ꜧ' => 'ꜧ',
'Ꜩ' => 'ꜩ',
'Ꜫ' => 'ꜫ',
'Ꜭ' => 'ꜭ',
'Ꜯ' => 'ꜯ',
'Ꜳ' => 'ꜳ',
'Ꜵ' => 'ꜵ',
'Ꜷ' => 'ꜷ',
'Ꜹ' => 'ꜹ',
'Ꜻ' => 'ꜻ',
'Ꜽ' => 'ꜽ',
'Ꜿ' => 'ꜿ',
'Ꝁ' => 'ꝁ',
'Ꝃ' => 'ꝃ',
'Ꝅ' => 'ꝅ',
'Ꝇ' => 'ꝇ',
'Ꝉ' => 'ꝉ',
'Ꝋ' => 'ꝋ',
'Ꝍ' => 'ꝍ',
'Ꝏ' => 'ꝏ',
'Ꝑ' => 'ꝑ',
'Ꝓ' => 'ꝓ',
'Ꝕ' => 'ꝕ',
'Ꝗ' => 'ꝗ',
'Ꝙ' => 'ꝙ',
'' => 'ꝛ',
'Ꝝ' => 'ꝝ',
'Ꝟ' => 'ꝟ',
'Ꝡ' => 'ꝡ',
'Ꝣ' => 'ꝣ',
'Ꝥ' => 'ꝥ',
'Ꝧ' => 'ꝧ',
'Ꝩ' => 'ꝩ',
'' => 'ꝫ',
'Ꝭ' => 'ꝭ',
'' => 'ꝯ',
'Ꝺ' => 'ꝺ',
'Ꝼ' => 'ꝼ',
'Ᵹ' => 'ᵹ',
'Ꝿ' => 'ꝿ',
'Ꞁ' => 'ꞁ',
'Ꞃ' => 'ꞃ',
'Ꞅ' => 'ꞅ',
'Ꞇ' => 'ꞇ',
'Ꞌ' => '',
'Ɥ' => 'ɥ',
'Ꞑ' => 'ꞑ',
'Ꞓ' => 'ꞓ',
'Ꞗ' => 'ꞗ',
'' => '',
'Ꞛ' => 'ꞛ',
'Ꞝ' => 'ꞝ',
'Ꞟ' => '',
'Ꞡ' => 'ꞡ',
'Ꞣ' => 'ꞣ',
'Ꞥ' => 'ꞥ',
'Ꞧ' => 'ꞧ',
'Ꞩ' => 'ꞩ',
'Ɦ' => 'ɦ',
'' => 'ɜ',
'Ɡ' => 'ɡ',
'Ɬ' => 'ɬ',
'Ɪ' => 'ɪ',
'Ʞ' => 'ʞ',
'Ʇ' => 'ʇ',
'' => 'ʝ',
'' => 'ꭓ',
'' => 'ꞵ',
'Ꞷ' => 'ꞷ',
'Ꞹ' => 'ꞹ',
'Ꞻ' => 'ꞻ',
'Ꞽ' => 'ꞽ',
'Ꞿ' => 'ꞿ',
'Ꟃ' => 'ꟃ',
'Ꞔ' => 'ꞔ',
'Ʂ' => 'ʂ',
'Ᶎ' => 'ᶎ',
'Ꟈ' => 'ꟈ',
'Ꟊ' => 'ꟊ',
'Ꟶ' => 'ꟶ',
'' => '',
'' => '',
'' => '',
'' => '',
'' => '',
'' => '',
'' => '',
'' => '',
'' => '',
'' => '',
'' => '',
'' => '',
'' => '',
'' => '',
'' => '',
'' => '',
'' => '',
'' => '',
'' => '',
'' => '',
'' => '',
'' => '',
'' => '',
'' => '',
'' => '',
'' => '',
'𐐀' => '𐐨',
'𐐁' => '𐐩',
'𐐂' => '𐐪',
'𐐃' => '𐐫',
'𐐄' => '𐐬',
'𐐅' => '𐐭',
'𐐆' => '𐐮',
'𐐇' => '𐐯',
'𐐈' => '𐐰',
'𐐉' => '𐐱',
'𐐊' => '𐐲',
'𐐋' => '𐐳',
'𐐌' => '𐐴',
'𐐍' => '𐐵',
'𐐎' => '𐐶',
'𐐏' => '𐐷',
'𐐐' => '𐐸',
'𐐑' => '𐐹',
'𐐒' => '𐐺',
'𐐓' => '𐐻',
'𐐔' => '𐐼',
'𐐕' => '𐐽',
'𐐖' => '𐐾',
'𐐗' => '𐐿',
'𐐘' => '𐑀',
'𐐙' => '𐑁',
'𐐚' => '𐑂',
'𐐛' => '𐑃',
'𐐜' => '𐑄',
'𐐝' => '𐑅',
'𐐞' => '𐑆',
'𐐟' => '𐑇',
'𐐠' => '𐑈',
'𐐡' => '𐑉',
'𐐢' => '𐑊',
'𐐣' => '𐑋',
'𐐤' => '𐑌',
'𐐥' => '𐑍',
'𐐦' => '𐑎',
'𐐧' => '𐑏',
'𐒰' => '𐓘',
'𐒱' => '𐓙',
'𐒲' => '𐓚',
'𐒳' => '𐓛',
'𐒴' => '𐓜',
'𐒵' => '𐓝',
'𐒶' => '𐓞',
'𐒷' => '𐓟',
'𐒸' => '𐓠',
'𐒹' => '𐓡',
'𐒺' => '𐓢',
'𐒻' => '𐓣',
'𐒼' => '𐓤',
'𐒽' => '𐓥',
'𐒾' => '𐓦',
'𐒿' => '𐓧',
'𐓀' => '𐓨',
'𐓁' => '𐓩',
'𐓂' => '𐓪',
'𐓃' => '𐓫',
'𐓄' => '𐓬',
'𐓅' => '𐓭',
'𐓆' => '𐓮',
'𐓇' => '𐓯',
'𐓈' => '𐓰',
'𐓉' => '𐓱',
'𐓊' => '𐓲',
'𐓋' => '𐓳',
'𐓌' => '𐓴',
'𐓍' => '𐓵',
'𐓎' => '𐓶',
'𐓏' => '𐓷',
'𐓐' => '𐓸',
'𐓑' => '𐓹',
'𐓒' => '𐓺',
'𐓓' => '𐓻',
'𐲀' => '𐳀',
'𐲁' => '𐳁',
'𐲂' => '𐳂',
'𐲃' => '𐳃',
'𐲄' => '𐳄',
'𐲅' => '𐳅',
'𐲆' => '𐳆',
'𐲇' => '𐳇',
'𐲈' => '𐳈',
'𐲉' => '𐳉',
'𐲊' => '𐳊',
'𐲋' => '𐳋',
'𐲌' => '𐳌',
'𐲍' => '𐳍',
'𐲎' => '𐳎',
'𐲏' => '𐳏',
'𐲐' => '𐳐',
'𐲑' => '𐳑',
'𐲒' => '𐳒',
'𐲓' => '𐳓',
'𐲔' => '𐳔',
'𐲕' => '𐳕',
'𐲖' => '𐳖',
'𐲗' => '𐳗',
'𐲘' => '𐳘',
'𐲙' => '𐳙',
'𐲚' => '𐳚',
'𐲛' => '𐳛',
'𐲜' => '𐳜',
'𐲝' => '𐳝',
'𐲞' => '𐳞',
'𐲟' => '𐳟',
'𐲠' => '𐳠',
'𐲡' => '𐳡',
'𐲢' => '𐳢',
'𐲣' => '𐳣',
'𐲤' => '𐳤',
'𐲥' => '𐳥',
'𐲦' => '𐳦',
'𐲧' => '𐳧',
'𐲨' => '𐳨',
'𐲩' => '𐳩',
'𐲪' => '𐳪',
'𐲫' => '𐳫',
'𐲬' => '𐳬',
'𐲭' => '𐳭',
'𐲮' => '𐳮',
'𐲯' => '𐳯',
'𐲰' => '𐳰',
'𐲱' => '𐳱',
'𐲲' => '𐳲',
'𑢠' => '𑣀',
'𑢡' => '𑣁',
'𑢢' => '𑣂',
'𑢣' => '𑣃',
'𑢤' => '𑣄',
'𑢥' => '𑣅',
'𑢦' => '𑣆',
'𑢧' => '𑣇',
'𑢨' => '𑣈',
'𑢩' => '𑣉',
'𑢪' => '𑣊',
'𑢫' => '𑣋',
'𑢬' => '𑣌',
'𑢭' => '𑣍',
'𑢮' => '𑣎',
'𑢯' => '𑣏',
'𑢰' => '𑣐',
'𑢱' => '𑣑',
'𑢲' => '𑣒',
'𑢳' => '𑣓',
'𑢴' => '𑣔',
'𑢵' => '𑣕',
'𑢶' => '𑣖',
'𑢷' => '𑣗',
'𑢸' => '𑣘',
'𑢹' => '𑣙',
'𑢺' => '𑣚',
'𑢻' => '𑣛',
'𑢼' => '𑣜',
'𑢽' => '𑣝',
'𑢾' => '𑣞',
'𑢿' => '𑣟',
'𖹀' => '𖹠',
'𖹁' => '𖹡',
'𖹂' => '𖹢',
'𖹃' => '𖹣',
'𖹄' => '𖹤',
'𖹅' => '𖹥',
'𖹆' => '𖹦',
'𖹇' => '𖹧',
'𖹈' => '𖹨',
'𖹉' => '𖹩',
'𖹊' => '𖹪',
'𖹋' => '𖹫',
'𖹌' => '𖹬',
'𖹍' => '𖹭',
'𖹎' => '𖹮',
'𖹏' => '𖹯',
'𖹐' => '𖹰',
'𖹑' => '𖹱',
'𖹒' => '𖹲',
'𖹓' => '𖹳',
'𖹔' => '𖹴',
'𖹕' => '𖹵',
'𖹖' => '𖹶',
'𖹗' => '𖹷',
'𖹘' => '𖹸',
'𖹙' => '𖹹',
'𖹚' => '𖹺',
'𖹛' => '𖹻',
'𖹜' => '𖹼',
'𖹝' => '𖹽',
'𖹞' => '𖹾',
'𖹟' => '𖹿',
'𞤀' => '𞤢',
'𞤁' => '𞤣',
'𞤂' => '𞤤',
'𞤃' => '𞤥',
'𞤄' => '𞤦',
'𞤅' => '𞤧',
'𞤆' => '𞤨',
'𞤇' => '𞤩',
'𞤈' => '𞤪',
'𞤉' => '𞤫',
'𞤊' => '𞤬',
'𞤋' => '𞤭',
'𞤌' => '𞤮',
'𞤍' => '𞤯',
'𞤎' => '𞤰',
'𞤏' => '𞤱',
'𞤐' => '𞤲',
'𞤑' => '𞤳',
'𞤒' => '𞤴',
'𞤓' => '𞤵',
'𞤔' => '𞤶',
'𞤕' => '𞤷',
'𞤖' => '𞤸',
'𞤗' => '𞤹',
'𞤘' => '𞤺',
'𞤙' => '𞤻',
'𞤚' => '𞤼',
'𞤛' => '𞤽',
'𞤜' => '𞤾',
'𞤝' => '𞤿',
'𞤞' => '𞥀',
'𞤟' => '𞥁',
'𞤠' => '𞥂',
'𞤡' => '𞥃',
);
<?php
// from Case_Ignorable in https://unicode.org/Public/UNIDATA/DerivedCoreProperties.txt
return '/(?<![\x{0027}\x{002E}\x{003A}\x{005E}\x{0060}\x{00A8}\x{00AD}\x{00AF}\x{00B4}\x{00B7}\x{00B8}\x{02B0}-\x{02C1}\x{02C2}-\x{02C5}\x{02C6}-\x{02D1}\x{02D2}-\x{02DF}\x{02E0}-\x{02E4}\x{02E5}-\x{02EB}\x{02EC}\x{02ED}\x{02EE}\x{02EF}-\x{02FF}\x{0300}-\x{036F}\x{0374}\x{0375}\x{037A}\x{0384}-\x{0385}\x{0387}\x{0483}-\x{0487}\x{0488}-\x{0489}\x{0559}\x{0591}-\x{05BD}\x{05BF}\x{05C1}-\x{05C2}\x{05C4}-\x{05C5}\x{05C7}\x{05F4}\x{0600}-\x{0605}\x{0610}-\x{061A}\x{061C}\x{0640}\x{064B}-\x{065F}\x{0670}\x{06D6}-\x{06DC}\x{06DD}\x{06DF}-\x{06E4}\x{06E5}-\x{06E6}\x{06E7}-\x{06E8}\x{06EA}-\x{06ED}\x{070F}\x{0711}\x{0730}-\x{074A}\x{07A6}-\x{07B0}\x{07EB}-\x{07F3}\x{07F4}-\x{07F5}\x{07FA}\x{07FD}\x{0816}-\x{0819}\x{081A}\x{081B}-\x{0823}\x{0824}\x{0825}-\x{0827}\x{0828}\x{0829}-\x{082D}\x{0859}-\x{085B}\x{08D3}-\x{08E1}\x{08E2}\x{08E3}-\x{0902}\x{093A}\x{093C}\x{0941}-\x{0948}\x{094D}\x{0951}-\x{0957}\x{0962}-\x{0963}\x{0971}\x{0981}\x{09BC}\x{09C1}-\x{09C4}\x{09CD}\x{09E2}-\x{09E3}\x{09FE}\x{0A01}-\x{0A02}\x{0A3C}\x{0A41}-\x{0A42}\x{0A47}-\x{0A48}\x{0A4B}-\x{0A4D}\x{0A51}\x{0A70}-\x{0A71}\x{0A75}\x{0A81}-\x{0A82}\x{0ABC}\x{0AC1}-\x{0AC5}\x{0AC7}-\x{0AC8}\x{0ACD}\x{0AE2}-\x{0AE3}\x{0AFA}-\x{0AFF}\x{0B01}\x{0B3C}\x{0B3F}\x{0B41}-\x{0B44}\x{0B4D}\x{0B56}\x{0B62}-\x{0B63}\x{0B82}\x{0BC0}\x{0BCD}\x{0C00}\x{0C04}\x{0C3E}-\x{0C40}\x{0C46}-\x{0C48}\x{0C4A}-\x{0C4D}\x{0C55}-\x{0C56}\x{0C62}-\x{0C63}\x{0C81}\x{0CBC}\x{0CBF}\x{0CC6}\x{0CCC}-\x{0CCD}\x{0CE2}-\x{0CE3}\x{0D00}-\x{0D01}\x{0D3B}-\x{0D3C}\x{0D41}-\x{0D44}\x{0D4D}\x{0D62}-\x{0D63}\x{0DCA}\x{0DD2}-\x{0DD4}\x{0DD6}\x{0E31}\x{0E34}-\x{0E3A}\x{0E46}\x{0E47}-\x{0E4E}\x{0EB1}\x{0EB4}-\x{0EB9}\x{0EBB}-\x{0EBC}\x{0EC6}\x{0EC8}-\x{0ECD}\x{0F18}-\x{0F19}\x{0F35}\x{0F37}\x{0F39}\x{0F71}-\x{0F7E}\x{0F80}-\x{0F84}\x{0F86}-\x{0F87}\x{0F8D}-\x{0F97}\x{0F99}-\x{0FBC}\x{0FC6}\x{102D}-\x{1030}\x{1032}-\x{1037}\x{1039}-\x{103A}\x{103D}-\x{103E}\x{1058}-\x{1059}\x{105E}-\x{1060}\x{1071}-\x{1074}\x{1082}\x{1085}-\x{1086}\x{108D}\x{109D}\x{10FC}\x{135D}-\x{135F}\x{1712}-\x{1714}\x{1732}-\x{1734}\x{1752}-\x{1753}\x{1772}-\x{1773}\x{17B4}-\x{17B5}\x{17B7}-\x{17BD}\x{17C6}\x{17C9}-\x{17D3}\x{17D7}\x{17DD}\x{180B}-\x{180D}\x{180E}\x{1843}\x{1885}-\x{1886}\x{18A9}\x{1920}-\x{1922}\x{1927}-\x{1928}\x{1932}\x{1939}-\x{193B}\x{1A17}-\x{1A18}\x{1A1B}\x{1A56}\x{1A58}-\x{1A5E}\x{1A60}\x{1A62}\x{1A65}-\x{1A6C}\x{1A73}-\x{1A7C}\x{1A7F}\x{1AA7}\x{1AB0}-\x{1ABD}\x{1ABE}\x{1B00}-\x{1B03}\x{1B34}\x{1B36}-\x{1B3A}\x{1B3C}\x{1B42}\x{1B6B}-\x{1B73}\x{1B80}-\x{1B81}\x{1BA2}-\x{1BA5}\x{1BA8}-\x{1BA9}\x{1BAB}-\x{1BAD}\x{1BE6}\x{1BE8}-\x{1BE9}\x{1BED}\x{1BEF}-\x{1BF1}\x{1C2C}-\x{1C33}\x{1C36}-\x{1C37}\x{1C78}-\x{1C7D}\x{1CD0}-\x{1CD2}\x{1CD4}-\x{1CE0}\x{1CE2}-\x{1CE8}\x{1CED}\x{1CF4}\x{1CF8}-\x{1CF9}\x{1D2C}-\x{1D6A}\x{1D78}\x{1D9B}-\x{1DBF}\x{1DC0}-\x{1DF9}\x{1DFB}-\x{1DFF}\x{1FBD}\x{1FBF}-\x{1FC1}\x{1FCD}-\x{1FCF}\x{1FDD}-\x{1FDF}\x{1FED}-\x{1FEF}\x{1FFD}-\x{1FFE}\x{200B}-\x{200F}\x{2018}\x{2019}\x{2024}\x{2027}\x{202A}-\x{202E}\x{2060}-\x{2064}\x{2066}-\x{206F}\x{2071}\x{207F}\x{2090}-\x{209C}\x{20D0}-\x{20DC}\x{20DD}-\x{20E0}\x{20E1}\x{20E2}-\x{20E4}\x{20E5}-\x{20F0}\x{2C7C}-\x{2C7D}\x{2CEF}-\x{2CF1}\x{2D6F}\x{2D7F}\x{2DE0}-\x{2DFF}\x{2E2F}\x{3005}\x{302A}-\x{302D}\x{3031}-\x{3035}\x{303B}\x{3099}-\x{309A}\x{309B}-\x{309C}\x{309D}-\x{309E}\x{30FC}-\x{30FE}\x{A015}\x{A4F8}-\x{A4FD}\x{A60C}\x{A66F}\x{A670}-\x{A672}\x{A674}-\x{A67D}\x{A67F}\x{A69C}-\x{A69D}\x{A69E}-\x{A69F}\x{A6F0}-\x{A6F1}\x{A700}-\x{A716}\x{A717}-\x{A71F}\x{A720}-\x{A721}\x{A770}\x{A788}\x{A789}-\x{A78A}\x{A7F8}-\x{A7F9}\x{A802}\x{A806}\x{A80B}\x{A825}-\x{A826}\x{A8C4}-\x{A8C5}\x{A8E0}-\x{A8F1}\x{A8FF}\x{A926}-\x{A92D}\x{A947}-\x{A951}\x{A980}-\x{A982}\x{A9B3}\x{A9B6}-\x{A9B9}\x{A9BC}\x{A9CF}\x{A9E5}\x{A9E6}\x{AA29}-\x{AA2E}\x{AA31}-\x{AA32}\x{AA35}-\x{AA36}\x{AA43}\x{AA4C}\x{AA70}\x{AA7C}\x{AAB0}\x{AAB2}-\x{AAB4}\x{AAB7}-\x{AAB8}\x{AABE}-\x{AABF}\x{AAC1}\x{AADD}\x{AAEC}-\x{AAED}\x{AAF3}-\x{AAF4}\x{AAF6}\x{AB5B}\x{AB5C}-\x{AB5F}\x{ABE5}\x{ABE8}\x{ABED}\x{FB1E}\x{FBB2}-\x{FBC1}\x{FE00}-\x{FE0F}\x{FE13}\x{FE20}-\x{FE2F}\x{FE52}\x{FE55}\x{FEFF}\x{FF07}\x{FF0E}\x{FF1A}\x{FF3E}\x{FF40}\x{FF70}\x{FF9E}-\x{FF9F}\x{FFE3}\x{FFF9}-\x{FFFB}\x{101FD}\x{102E0}\x{10376}-\x{1037A}\x{10A01}-\x{10A03}\x{10A05}-\x{10A06}\x{10A0C}-\x{10A0F}\x{10A38}-\x{10A3A}\x{10A3F}\x{10AE5}-\x{10AE6}\x{10D24}-\x{10D27}\x{10F46}-\x{10F50}\x{11001}\x{11038}-\x{11046}\x{1107F}-\x{11081}\x{110B3}-\x{110B6}\x{110B9}-\x{110BA}\x{110BD}\x{110CD}\x{11100}-\x{11102}\x{11127}-\x{1112B}\x{1112D}-\x{11134}\x{11173}\x{11180}-\x{11181}\x{111B6}-\x{111BE}\x{111C9}-\x{111CC}\x{1122F}-\x{11231}\x{11234}\x{11236}-\x{11237}\x{1123E}\x{112DF}\x{112E3}-\x{112EA}\x{11300}-\x{11301}\x{1133B}-\x{1133C}\x{11340}\x{11366}-\x{1136C}\x{11370}-\x{11374}\x{11438}-\x{1143F}\x{11442}-\x{11444}\x{11446}\x{1145E}\x{114B3}-\x{114B8}\x{114BA}\x{114BF}-\x{114C0}\x{114C2}-\x{114C3}\x{115B2}-\x{115B5}\x{115BC}-\x{115BD}\x{115BF}-\x{115C0}\x{115DC}-\x{115DD}\x{11633}-\x{1163A}\x{1163D}\x{1163F}-\x{11640}\x{116AB}\x{116AD}\x{116B0}-\x{116B5}\x{116B7}\x{1171D}-\x{1171F}\x{11722}-\x{11725}\x{11727}-\x{1172B}\x{1182F}-\x{11837}\x{11839}-\x{1183A}\x{11A01}-\x{11A0A}\x{11A33}-\x{11A38}\x{11A3B}-\x{11A3E}\x{11A47}\x{11A51}-\x{11A56}\x{11A59}-\x{11A5B}\x{11A8A}-\x{11A96}\x{11A98}-\x{11A99}\x{11C30}-\x{11C36}\x{11C38}-\x{11C3D}\x{11C3F}\x{11C92}-\x{11CA7}\x{11CAA}-\x{11CB0}\x{11CB2}-\x{11CB3}\x{11CB5}-\x{11CB6}\x{11D31}-\x{11D36}\x{11D3A}\x{11D3C}-\x{11D3D}\x{11D3F}-\x{11D45}\x{11D47}\x{11D90}-\x{11D91}\x{11D95}\x{11D97}\x{11EF3}-\x{11EF4}\x{16AF0}-\x{16AF4}\x{16B30}-\x{16B36}\x{16B40}-\x{16B43}\x{16F8F}-\x{16F92}\x{16F93}-\x{16F9F}\x{16FE0}-\x{16FE1}\x{1BC9D}-\x{1BC9E}\x{1BCA0}-\x{1BCA3}\x{1D167}-\x{1D169}\x{1D173}-\x{1D17A}\x{1D17B}-\x{1D182}\x{1D185}-\x{1D18B}\x{1D1AA}-\x{1D1AD}\x{1D242}-\x{1D244}\x{1DA00}-\x{1DA36}\x{1DA3B}-\x{1DA6C}\x{1DA75}\x{1DA84}\x{1DA9B}-\x{1DA9F}\x{1DAA1}-\x{1DAAF}\x{1E000}-\x{1E006}\x{1E008}-\x{1E018}\x{1E01B}-\x{1E021}\x{1E023}-\x{1E024}\x{1E026}-\x{1E02A}\x{1E8D0}-\x{1E8D6}\x{1E944}-\x{1E94A}\x{1F3FB}-\x{1F3FF}\x{E0001}\x{E0020}-\x{E007F}\x{E0100}-\x{E01EF}])(\pL)(\pL*+)/u';
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Symfony\Polyfill\Php73 as p;
if (PHP_VERSION_ID >= 70300) {
return;
}
if (!function_exists('is_countable')) {
function is_countable($value) { return is_array($value) || $value instanceof Countable || $value instanceof ResourceBundle || $value instanceof SimpleXmlElement; }
}
if (!function_exists('hrtime')) {
require_once __DIR__.'/Php73.php';
p\Php73::$startAt = (int) microtime(true);
function hrtime($as_number = false) { return p\Php73::hrtime($as_number ); }
}
if (!function_exists('array_key_first')) {
function array_key_first(array $array) { foreach ($array as $key => $value) { return $key; } }
}
if (!function_exists('array_key_last')) {
function array_key_last(array $array) { return key(array_slice($array, -1, 1, true)); }
}
Copyright (c) 2018-2019 Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Polyfill\Php73;
/**
* @author Gabriel Caruso <carusogabriel34@gmail.com>
* @author Ion Bazan <ion.bazan@gmail.com>
*
* @internal
*/
final class Php73
{
public static $startAt = 1533462603;
/**
* @param bool $asNum
*
* @return array|float|int
*/
public static function hrtime($asNum = false)
{
$ns = microtime(false);
$s = substr($ns, 11) - self::$startAt;
$ns = 1E9 * (float) $ns;
if ($asNum) {
$ns += $s * 1E9;
return \PHP_INT_SIZE === 4 ? $ns : (int) $ns;
}
return array($s, (int) $ns);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
class JsonException extends Exception
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Symfony\Polyfill\Php80 as p;
if (PHP_VERSION_ID >= 80000) {
return;
}
if (!defined('FILTER_VALIDATE_BOOL') && defined('FILTER_VALIDATE_BOOLEAN')) {
define('FILTER_VALIDATE_BOOL', FILTER_VALIDATE_BOOLEAN);
}
if (!function_exists('fdiv')) {
function fdiv(float $num1, float $num2): float { return p\Php80::fdiv($num1, $num2); }
}
if (!function_exists('preg_last_error_msg')) {
function preg_last_error_msg(): string { return p\Php80::preg_last_error_msg(); }
}
if (!function_exists('str_contains')) {
function str_contains(string $haystack, string $needle): bool { return p\Php80::str_contains($haystack, $needle); }
}
if (!function_exists('str_starts_with')) {
function str_starts_with(string $haystack, string $needle): bool { return p\Php80::str_starts_with($haystack, $needle); }
}
if (!function_exists('str_ends_with')) {
function str_ends_with(string $haystack, string $needle): bool { return p\Php80::str_ends_with($haystack, $needle); }
}
if (!function_exists('get_debug_type')) {
function get_debug_type($value): string { return p\Php80::get_debug_type($value); }
}
if (!function_exists('get_resource_id')) {
function get_resource_id($res): int { return p\Php80::get_resource_id($res); }
}
Copyright (c) 2020 Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
<?php
if (\PHP_VERSION_ID < 80000) {
interface Stringable
{
/**
* @return string
*/
public function __toString();
}
}
<?php
#[Attribute(Attribute::TARGET_CLASS)]
final class Attribute
{
const TARGET_CLASS = 1;
const TARGET_FUNCTION = 2;
const TARGET_METHOD = 4;
const TARGET_PROPERTY = 8;
const TARGET_CLASS_CONSTANT = 16;
const TARGET_PARAMETER = 32;
const TARGET_ALL = 63;
const IS_REPEATABLE = 64;
/** @var int */
public $flags;
public function __construct(int $flags = Attribute::TARGET_ALL)
{
$this->flags = $flags;
}
}
<?php
class UnhandledMatchError extends Error
{
}
<?php
class ValueError extends Error
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Polyfill\Php80;
/**
* @author Ion Bazan <ion.bazan@gmail.com>
* @author Nico Oelgart <nicoswd@gmail.com>
* @author Nicolas Grekas <p@tchwork.com>
*
* @internal
*/
final class Php80
{
public static function fdiv(float $dividend, float $divisor): float
{
return @($dividend / $divisor);
}
public static function get_debug_type($value): string
{
switch (true) {
case null === $value: return 'null';
case \is_bool($value): return 'bool';
case \is_string($value): return 'string';
case \is_array($value): return 'array';
case \is_int($value): return 'int';
case \is_float($value): return 'float';
case \is_object($value): break;
case $value instanceof \__PHP_Incomplete_Class: return '__PHP_Incomplete_Class';
default:
if (null === $type = @get_resource_type($value)) {
return 'unknown';
}
if ('Unknown' === $type) {
$type = 'closed';
}
return "resource ($type)";
}
$class = \get_class($value);
if (false === strpos($class, '@')) {
return $class;
}
return (get_parent_class($class) ?: key(class_implements($class)) ?: 'class').'@anonymous';
}
public static function get_resource_id($res): int
{
if (!\is_resource($res) && null === @get_resource_type($res)) {
throw new \TypeError(sprintf('Argument 1 passed to get_resource_id() must be of the type resource, %s given', get_debug_type($res)));
}
return (int) $res;
}
public static function preg_last_error_msg(): string
{
switch (preg_last_error()) {
case PREG_INTERNAL_ERROR:
return 'Internal error';
case PREG_BAD_UTF8_ERROR:
return 'Malformed UTF-8 characters, possibly incorrectly encoded';
case PREG_BAD_UTF8_OFFSET_ERROR:
return 'The offset did not correspond to the beginning of a valid UTF-8 code point';
case PREG_BACKTRACK_LIMIT_ERROR:
return 'Backtrack limit exhausted';
case PREG_RECURSION_LIMIT_ERROR:
return 'Recursion limit exhausted';
case PREG_JIT_STACKLIMIT_ERROR:
return 'JIT stack limit exhausted';
case PREG_NO_ERROR:
return 'No error';
default:
return 'Unknown error';
}
}
public static function str_contains(string $haystack, string $needle): bool
{
return '' === $needle || false !== strpos($haystack, $needle);
}
public static function str_starts_with(string $haystack, string $needle): bool
{
return 0 === \strncmp($haystack, $needle, \strlen($needle));
}
public static function str_ends_with(string $haystack, string $needle): bool
{
return '' === $needle || ('' !== $haystack && 0 === \substr_compare($haystack, $needle, -\strlen($needle)));
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Contracts\Service\Attribute;
use Attribute;
/**
* A required dependency.
*
* This attribute indicates that a property holds a required dependency. The annotated property or method should be
* considered during the instantiation process of the containing class.
*
* @author Alexander M. Turek <me@derrabus.de>
*/
#[Attribute(Attribute::TARGET_METHOD | Attribute::TARGET_PROPERTY)]
final class Required
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Contracts\Service;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
// Help opcache.preload discover always-needed symbols
class_exists(ContainerExceptionInterface::class);
class_exists(NotFoundExceptionInterface::class);
/**
* A trait to help implement ServiceProviderInterface.
*
* @author Robin Chalas <robin.chalas@gmail.com>
* @author Nicolas Grekas <p@tchwork.com>
*/
trait ServiceLocatorTrait
{
private $factories;
private $loading = [];
private $providedTypes;
/**
* @param callable[] $factories
*/
public function __construct(array $factories)
{
$this->factories = $factories;
}
/**
* {@inheritdoc}
*
* @return bool
*/
public function has($id)
{
return isset($this->factories[$id]);
}
/**
* {@inheritdoc}
*/
public function get($id)
{
if (!isset($this->factories[$id])) {
throw $this->createNotFoundException($id);
}
if (isset($this->loading[$id])) {
$ids = array_values($this->loading);
$ids = \array_slice($this->loading, array_search($id, $ids));
$ids[] = $id;
throw $this->createCircularReferenceException($id, $ids);
}
$this->loading[$id] = $id;
try {
return $this->factories[$id]($this);
} finally {
unset($this->loading[$id]);
}
}
/**
* {@inheritdoc}
*/
public function getProvidedServices(): array
{
if (null === $this->providedTypes) {
$this->providedTypes = [];
foreach ($this->factories as $name => $factory) {
if (!\is_callable($factory)) {
$this->providedTypes[$name] = '?';
} else {
$type = (new \ReflectionFunction($factory))->getReturnType();
$this->providedTypes[$name] = $type ? ($type->allowsNull() ? '?' : '').($type instanceof \ReflectionNamedType ? $type->getName() : $type) : '?';
}
}
}
return $this->providedTypes;
}
private function createNotFoundException(string $id): NotFoundExceptionInterface
{
if (!$alternatives = array_keys($this->factories)) {
$message = 'is empty...';
} else {
$last = array_pop($alternatives);
if ($alternatives) {
$message = sprintf('only knows about the "%s" and "%s" services.', implode('", "', $alternatives), $last);
} else {
$message = sprintf('only knows about the "%s" service.', $last);
}
}
if ($this->loading) {
$message = sprintf('The service "%s" has a dependency on a non-existent service "%s". This locator %s', end($this->loading), $id, $message);
} else {
$message = sprintf('Service "%s" not found: the current service locator %s', $id, $message);
}
return new class($message) extends \InvalidArgumentException implements NotFoundExceptionInterface {
};
}
private function createCircularReferenceException(string $id, array $path): ContainerExceptionInterface
{
return new class(sprintf('Circular reference detected for service "%s", path: "%s".', $id, implode(' -> ', $path))) extends \RuntimeException implements ContainerExceptionInterface {
};
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Contracts\Service;
/**
* A ServiceSubscriber exposes its dependencies via the static {@link getSubscribedServices} method.
*
* The getSubscribedServices method returns an array of service types required by such instances,
* optionally keyed by the service names used internally. Service types that start with an interrogation
* mark "?" are optional, while the other ones are mandatory service dependencies.
*
* The injected service locators SHOULD NOT allow access to any other services not specified by the method.
*
* It is expected that ServiceSubscriber instances consume PSR-11-based service locators internally.
* This interface does not dictate any injection method for these service locators, although constructor
* injection is recommended.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
interface ServiceSubscriberInterface
{
/**
* Returns an array of service types required by such instances, optionally keyed by the service names used internally.
*
* For mandatory dependencies:
*
* * ['logger' => 'Psr\Log\LoggerInterface'] means the objects use the "logger" name
* internally to fetch a service which must implement Psr\Log\LoggerInterface.
* * ['loggers' => 'Psr\Log\LoggerInterface[]'] means the objects use the "loggers" name
* internally to fetch an iterable of Psr\Log\LoggerInterface instances.
* * ['Psr\Log\LoggerInterface'] is a shortcut for
* * ['Psr\Log\LoggerInterface' => 'Psr\Log\LoggerInterface']
*
* otherwise:
*
* * ['logger' => '?Psr\Log\LoggerInterface'] denotes an optional dependency
* * ['loggers' => '?Psr\Log\LoggerInterface[]'] denotes an optional iterable dependency
* * ['?Psr\Log\LoggerInterface'] is a shortcut for
* * ['Psr\Log\LoggerInterface' => '?Psr\Log\LoggerInterface']
*
* @return array The required service types, optionally keyed by service names
*/
public static function getSubscribedServices();
}
Copyright (c) 2018-2020 Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Contracts\Service;
use Psr\Container\ContainerInterface;
/**
* A ServiceProviderInterface exposes the identifiers and the types of services provided by a container.
*
* @author Nicolas Grekas <p@tchwork.com>
* @author Mateusz Sip <mateusz.sip@gmail.com>
*/
interface ServiceProviderInterface extends ContainerInterface
{
/**
* Returns an associative array of service types keyed by the identifiers provided by the current container.
*
* Examples:
*
* * ['logger' => 'Psr\Log\LoggerInterface'] means the object provides a service named "logger" that implements Psr\Log\LoggerInterface
* * ['foo' => '?'] means the container provides service name "foo" of unspecified type
* * ['bar' => '?Bar\Baz'] means the container provides a service "bar" of type Bar\Baz|null
*
* @return string[] The provided service types, keyed by service names
*/
public function getProvidedServices(): array;
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Contracts\Service\Test;
use PHPUnit\Framework\TestCase;
use Psr\Container\ContainerInterface;
use Symfony\Contracts\Service\ServiceLocatorTrait;
abstract class ServiceLocatorTest extends TestCase
{
protected function getServiceLocator(array $factories)
{
return new class($factories) implements ContainerInterface {
use ServiceLocatorTrait;
};
}
public function testHas()
{
$locator = $this->getServiceLocator([
'foo' => function () { return 'bar'; },
'bar' => function () { return 'baz'; },
function () { return 'dummy'; },
]);
$this->assertTrue($locator->has('foo'));
$this->assertTrue($locator->has('bar'));
$this->assertFalse($locator->has('dummy'));
}
public function testGet()
{
$locator = $this->getServiceLocator([
'foo' => function () { return 'bar'; },
'bar' => function () { return 'baz'; },
]);
$this->assertSame('bar', $locator->get('foo'));
$this->assertSame('baz', $locator->get('bar'));
}
public function testGetDoesNotMemoize()
{
$i = 0;
$locator = $this->getServiceLocator([
'foo' => function () use (&$i) {
++$i;
return 'bar';
},
]);
$this->assertSame('bar', $locator->get('foo'));
$this->assertSame('bar', $locator->get('foo'));
$this->assertSame(2, $i);
}
public function testThrowsOnUndefinedInternalService()
{
if (!$this->getExpectedException()) {
$this->expectException('Psr\Container\NotFoundExceptionInterface');
$this->expectExceptionMessage('The service "foo" has a dependency on a non-existent service "bar". This locator only knows about the "foo" service.');
}
$locator = $this->getServiceLocator([
'foo' => function () use (&$locator) { return $locator->get('bar'); },
]);
$locator->get('foo');
}
public function testThrowsOnCircularReference()
{
$this->expectException('Psr\Container\ContainerExceptionInterface');
$this->expectExceptionMessage('Circular reference detected for service "bar", path: "bar -> baz -> bar".');
$locator = $this->getServiceLocator([
'foo' => function () use (&$locator) { return $locator->get('bar'); },
'bar' => function () use (&$locator) { return $locator->get('baz'); },
'baz' => function () use (&$locator) { return $locator->get('bar'); },
]);
$locator->get('foo');
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Contracts\Service;
/**
* Provides a way to reset an object to its initial state.
*
* When calling the "reset()" method on an object, it should be put back to its
* initial state. This usually means clearing any internal buffers and forwarding
* the call to internal dependencies. All properties of the object should be put
* back to the same state it had when it was first ready to use.
*
* This method could be called, for example, to recycle objects that are used as
* services, so that they can be used to handle several requests in the same
* process loop (note that we advise making your services stateless instead of
* implementing this interface when possible.)
*/
interface ResetInterface
{
public function reset();
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Contracts\Service;
use Psr\Container\ContainerInterface;
/**
* Implementation of ServiceSubscriberInterface that determines subscribed services from
* private method return types. Service ids are available as "ClassName::methodName".
*
* @author Kevin Bond <kevinbond@gmail.com>
*/
trait ServiceSubscriberTrait
{
/** @var ContainerInterface */
protected $container;
public static function getSubscribedServices(): array
{
static $services;
if (null !== $services) {
return $services;
}
$services = \is_callable(['parent', __FUNCTION__]) ? parent::getSubscribedServices() : [];
foreach ((new \ReflectionClass(self::class))->getMethods() as $method) {
if ($method->isStatic() || $method->isAbstract() || $method->isGenerator() || $method->isInternal() || $method->getNumberOfRequiredParameters()) {
continue;
}
if (self::class === $method->getDeclaringClass()->name && ($returnType = $method->getReturnType()) && !$returnType->isBuiltin()) {
$services[self::class.'::'.$method->name] = '?'.($returnType instanceof \ReflectionNamedType ? $returnType->getName() : $type);
}
}
return $services;
}
/**
* @required
*/
public function setContainer(ContainerInterface $container)
{
$this->container = $container;
if (\is_callable(['parent', __FUNCTION__])) {
return parent::setContainer($container);
}
return null;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\String\Inflector;
/**
* French inflector.
*
* This class does only inflect nouns; not adjectives nor composed words like "soixante-dix".
*/
final class FrenchInflector implements InflectorInterface
{
/**
* A list of all rules for pluralise.
*
* @see https://la-conjugaison.nouvelobs.com/regles/grammaire/le-pluriel-des-noms-121.php
*/
private static $pluralizeRegexp = [
// First entry: regexp
// Second entry: replacement
// Words finishing with "s", "x" or "z" are invariables
// Les mots finissant par "s", "x" ou "z" sont invariables
['/(s|x|z)$/i', '\1'],
// Words finishing with "eau" are pluralized with a "x"
// Les mots finissant par "eau" prennent tous un "x" au pluriel
['/(eau)$/i', '\1x'],
// Words finishing with "au" are pluralized with a "x" excepted "landau"
// Les mots finissant par "au" prennent un "x" au pluriel sauf "landau"
['/^(landau)$/i', '\1s'],
['/(au)$/i', '\1x'],
// Words finishing with "eu" are pluralized with a "x" excepted "pneu", "bleu", "émeu"
// Les mots finissant en "eu" prennent un "x" au pluriel sauf "pneu", "bleu", "émeu"
['/^(pneu|bleu|émeu)$/i', '\1s'],
['/(eu)$/i', '\1x'],
// Words finishing with "al" are pluralized with a "aux" excepted
// Les mots finissant en "al" se terminent en "aux" sauf
['/^(bal|carnaval|caracal|chacal|choral|corral|étal|festival|récital|val)$/i', '\1s'],
['/al$/i', '\1aux'],
// Aspirail, bail, corail, émail, fermail, soupirail, travail, vantail et vitrail font leur pluriel en -aux
['/^(aspir|b|cor|ém|ferm|soupir|trav|vant|vitr)ail$/i', '\1aux'],
// Bijou, caillou, chou, genou, hibou, joujou et pou qui prennent un x au pluriel
['/^(bij|caill|ch|gen|hib|jouj|p)ou$/i', '\1oux'],
// Invariable words
['/^(cinquante|soixante|mille)$/i', '\1'],
// French titles
['/^(mon|ma)(sieur|dame|demoiselle|seigneur)$/', 'mes\2s'],
['/^(Mon|Ma)(sieur|dame|demoiselle|seigneur)$/', 'Mes\2s'],
];
/**
* A list of all rules for singularize.
*/
private static $singularizeRegexp = [
// First entry: regexp
// Second entry: replacement
// Aspirail, bail, corail, émail, fermail, soupirail, travail, vantail et vitrail font leur pluriel en -aux
['/((aspir|b|cor|ém|ferm|soupir|trav|vant|vitr))aux$/i', '\1ail'],
// Words finishing with "eau" are pluralized with a "x"
// Les mots finissant par "eau" prennent tous un "x" au pluriel
['/(eau)x$/i', '\1'],
// Words finishing with "al" are pluralized with a "aux" expected
// Les mots finissant en "al" se terminent en "aux" sauf
['/(amir|anim|arsen|boc|can|capit|capor|chev|crist|génér|hopit|hôpit|idé|journ|littor|loc|m|mét|minér|princip|radic|termin)aux$/i', '\1al'],
// Words finishing with "au" are pluralized with a "x" excepted "landau"
// Les mots finissant par "au" prennent un "x" au pluriel sauf "landau"
['/(au)x$/i', '\1'],
// Words finishing with "eu" are pluralized with a "x" excepted "pneu", "bleu", "émeu"
// Les mots finissant en "eu" prennent un "x" au pluriel sauf "pneu", "bleu", "émeu"
['/(eu)x$/i', '\1'],
// Words finishing with "ou" are pluralized with a "s" excepted bijou, caillou, chou, genou, hibou, joujou, pou
// Les mots finissant par "ou" prennent un "s" sauf bijou, caillou, chou, genou, hibou, joujou, pou
['/(bij|caill|ch|gen|hib|jouj|p)oux$/i', '\1ou'],
// French titles
['/^mes(dame|demoiselle)s$/', 'ma\1'],
['/^Mes(dame|demoiselle)s$/', 'Ma\1'],
['/^mes(sieur|seigneur)s$/', 'mon\1'],
['/^Mes(sieur|seigneur)s$/', 'Mon\1'],
//Default rule
['/s$/i', ''],
];
/**
* A list of words which should not be inflected.
* This list is only used by singularize.
*/
private static $uninflected = '/^(abcès|accès|abus|albatros|anchois|anglais|autobus|bois|brebis|carquois|cas|chas|colis|concours|corps|cours|cyprès|décès|devis|discours|dos|embarras|engrais|entrelacs|excès|fils|fois|gâchis|gars|glas|héros|intrus|jars|jus|kermès|lacis|legs|lilas|marais|mars|matelas|mépris|mets|mois|mors|obus|os|palais|paradis|parcours|pardessus|pays|plusieurs|poids|pois|pouls|printemps|processus|progrès|puits|pus|rabais|radis|recors|recours|refus|relais|remords|remous|rictus|rhinocéros|repas|rubis|sas|secours|sens|souris|succès|talus|tapis|tas|taudis|temps|tiers|univers|velours|verglas|vernis|virus)$/i';
/**
* {@inheritdoc}
*/
public function singularize(string $plural): array
{
if ($this->isInflectedWord($plural)) {
return [$plural];
}
foreach (self::$singularizeRegexp as $rule) {
[$regexp, $replace] = $rule;
if (1 === preg_match($regexp, $plural)) {
return [preg_replace($regexp, $replace, $plural)];
}
}
return [$plural];
}
/**
* {@inheritdoc}
*/
public function pluralize(string $singular): array
{
if ($this->isInflectedWord($singular)) {
return [$singular];
}
foreach (self::$pluralizeRegexp as $rule) {
[$regexp, $replace] = $rule;
if (1 === preg_match($regexp, $singular)) {
return [preg_replace($regexp, $replace, $singular)];
}
}
return [$singular.'s'];
}
private function isInflectedWord(string $word): bool
{
return 1 === preg_match(self::$uninflected, $word);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\String\Inflector;
interface InflectorInterface
{
/**
* Returns the singular forms of a string.
*
* If the method can't determine the form with certainty, several possible singulars are returned.
*
* @return string[] An array of possible singular forms
*/
public function singularize(string $plural): array;
/**
* Returns the plural forms of a string.
*
* If the method can't determine the form with certainty, several possible plurals are returned.
*
* @return string[] An array of possible plural forms
*/
public function pluralize(string $singular): array;
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\String\Inflector;
final class EnglishInflector implements InflectorInterface
{
/**
* Map English plural to singular suffixes.
*
* @see http://english-zone.com/spelling/plurals.html
*/
private static $pluralMap = [
// First entry: plural suffix, reversed
// Second entry: length of plural suffix
// Third entry: Whether the suffix may succeed a vocal
// Fourth entry: Whether the suffix may succeed a consonant
// Fifth entry: singular suffix, normal
// bacteria (bacterium), criteria (criterion), phenomena (phenomenon)
['a', 1, true, true, ['on', 'um']],
// nebulae (nebula)
['ea', 2, true, true, 'a'],
// services (service)
['secivres', 8, true, true, 'service'],
// mice (mouse), lice (louse)
['eci', 3, false, true, 'ouse'],
// geese (goose)
['esee', 4, false, true, 'oose'],
// fungi (fungus), alumni (alumnus), syllabi (syllabus), radii (radius)
['i', 1, true, true, 'us'],
// men (man), women (woman)
['nem', 3, true, true, 'man'],
// children (child)
['nerdlihc', 8, true, true, 'child'],
// oxen (ox)
['nexo', 4, false, false, 'ox'],
// indices (index), appendices (appendix), prices (price)
['seci', 4, false, true, ['ex', 'ix', 'ice']],
// selfies (selfie)
['seifles', 7, true, true, 'selfie'],
// movies (movie)
['seivom', 6, true, true, 'movie'],
// feet (foot)
['teef', 4, true, true, 'foot'],
// geese (goose)
['eseeg', 5, true, true, 'goose'],
// teeth (tooth)
['hteet', 5, true, true, 'tooth'],
// news (news)
['swen', 4, true, true, 'news'],
// series (series)
['seires', 6, true, true, 'series'],
// babies (baby)
['sei', 3, false, true, 'y'],
// accesses (access), addresses (address), kisses (kiss)
['sess', 4, true, false, 'ss'],
// analyses (analysis), ellipses (ellipsis), fungi (fungus),
// neuroses (neurosis), theses (thesis), emphases (emphasis),
// oases (oasis), crises (crisis), houses (house), bases (base),
// atlases (atlas)
['ses', 3, true, true, ['s', 'se', 'sis']],
// objectives (objective), alternative (alternatives)
['sevit', 5, true, true, 'tive'],
// drives (drive)
['sevird', 6, false, true, 'drive'],
// lives (life), wives (wife)
['sevi', 4, false, true, 'ife'],
// moves (move)
['sevom', 5, true, true, 'move'],
// hooves (hoof), dwarves (dwarf), elves (elf), leaves (leaf), caves (cave), staves (staff)
['sev', 3, true, true, ['f', 've', 'ff']],
// axes (axis), axes (ax), axes (axe)
['sexa', 4, false, false, ['ax', 'axe', 'axis']],
// indexes (index), matrixes (matrix)
['sex', 3, true, false, 'x'],
// quizzes (quiz)
['sezz', 4, true, false, 'z'],
// bureaus (bureau)
['suae', 4, false, true, 'eau'],
// fees (fee), trees (tree), employees (employee)
['see', 3, true, true, 'ee'],
// roses (rose), garages (garage), cassettes (cassette),
// waltzes (waltz), heroes (hero), bushes (bush), arches (arch),
// shoes (shoe)
['se', 2, true, true, ['', 'e']],
// tags (tag)
['s', 1, true, true, ''],
// chateaux (chateau)
['xuae', 4, false, true, 'eau'],
// people (person)
['elpoep', 6, true, true, 'person'],
];
/**
* Map English singular to plural suffixes.
*
* @see http://english-zone.com/spelling/plurals.html
*/
private static $singularMap = [
// First entry: singular suffix, reversed
// Second entry: length of singular suffix
// Third entry: Whether the suffix may succeed a vocal
// Fourth entry: Whether the suffix may succeed a consonant
// Fifth entry: plural suffix, normal
// criterion (criteria)
['airetirc', 8, false, false, 'criterion'],
// nebulae (nebula)
['aluben', 6, false, false, 'nebulae'],
// children (child)
['dlihc', 5, true, true, 'children'],
// prices (price)
['eci', 3, false, true, 'ices'],
// services (service)
['ecivres', 7, true, true, 'services'],
// lives (life), wives (wife)
['efi', 3, false, true, 'ives'],
// selfies (selfie)
['eifles', 6, true, true, 'selfies'],
// movies (movie)
['eivom', 5, true, true, 'movies'],
// lice (louse)
['esuol', 5, false, true, 'lice'],
// mice (mouse)
['esuom', 5, false, true, 'mice'],
// geese (goose)
['esoo', 4, false, true, 'eese'],
// houses (house), bases (base)
['es', 2, true, true, 'ses'],
// geese (goose)
['esoog', 5, true, true, 'geese'],
// caves (cave)
['ev', 2, true, true, 'ves'],
// drives (drive)
['evird', 5, false, true, 'drives'],
// objectives (objective), alternative (alternatives)
['evit', 4, true, true, 'tives'],
// moves (move)
['evom', 4, true, true, 'moves'],
// staves (staff)
['ffats', 5, true, true, 'staves'],
// hooves (hoof), dwarves (dwarf), elves (elf), leaves (leaf)
['ff', 2, true, true, 'ffs'],
// hooves (hoof), dwarves (dwarf), elves (elf), leaves (leaf)
['f', 1, true, true, ['fs', 'ves']],
// arches (arch)
['hc', 2, true, true, 'ches'],
// bushes (bush)
['hs', 2, true, true, 'shes'],
// teeth (tooth)
['htoot', 5, true, true, 'teeth'],
// bacteria (bacterium), criteria (criterion), phenomena (phenomenon)
['mu', 2, true, true, 'a'],
// men (man), women (woman)
['nam', 3, true, true, 'men'],
// people (person)
['nosrep', 6, true, true, ['persons', 'people']],
// bacteria (bacterium), criteria (criterion), phenomena (phenomenon)
['noi', 3, true, true, 'ions'],
// seasons (season), treasons (treason), poisons (poison), lessons (lesson)
['nos', 3, true, true, 'sons'],
// bacteria (bacterium), criteria (criterion), phenomena (phenomenon)
['no', 2, true, true, 'a'],
// echoes (echo)
['ohce', 4, true, true, 'echoes'],
// heroes (hero)
['oreh', 4, true, true, 'heroes'],
// atlases (atlas)
['salta', 5, true, true, 'atlases'],
// irises (iris)
['siri', 4, true, true, 'irises'],
// analyses (analysis), ellipses (ellipsis), neuroses (neurosis)
// theses (thesis), emphases (emphasis), oases (oasis),
// crises (crisis)
['sis', 3, true, true, 'ses'],
// accesses (access), addresses (address), kisses (kiss)
['ss', 2, true, false, 'sses'],
// syllabi (syllabus)
['suballys', 8, true, true, 'syllabi'],
// buses (bus)
['sub', 3, true, true, 'buses'],
// circuses (circus)
['suc', 3, true, true, 'cuses'],
// fungi (fungus), alumni (alumnus), syllabi (syllabus), radii (radius)
['su', 2, true, true, 'i'],
// news (news)
['swen', 4, true, true, 'news'],
// feet (foot)
['toof', 4, true, true, 'feet'],
// chateaux (chateau), bureaus (bureau)
['uae', 3, false, true, ['eaus', 'eaux']],
// oxen (ox)
['xo', 2, false, false, 'oxen'],
// hoaxes (hoax)
['xaoh', 4, true, false, 'hoaxes'],
// indices (index)
['xedni', 5, false, true, ['indicies', 'indexes']],
// boxes (box)
['xo', 2, false, true, 'oxes'],
// indexes (index), matrixes (matrix)
['x', 1, true, false, ['cies', 'xes']],
// appendices (appendix)
['xi', 2, false, true, 'ices'],
// babies (baby)
['y', 1, false, true, 'ies'],
// quizzes (quiz)
['ziuq', 4, true, false, 'quizzes'],
// waltzes (waltz)
['z', 1, true, true, 'zes'],
];
/**
* A list of words which should not be inflected, reversed.
*/
private static $uninflected = [
'atad',
'reed',
'kcabdeef',
'hsif',
'ofni',
'esoom',
'seires',
'peehs',
'seiceps',
];
/**
* {@inheritdoc}
*/
public function singularize(string $plural): array
{
$pluralRev = strrev($plural);
$lowerPluralRev = strtolower($pluralRev);
$pluralLength = \strlen($lowerPluralRev);
// Check if the word is one which is not inflected, return early if so
if (\in_array($lowerPluralRev, self::$uninflected, true)) {
return [$plural];
}
// The outer loop iterates over the entries of the plural table
// The inner loop $j iterates over the characters of the plural suffix
// in the plural table to compare them with the characters of the actual
// given plural suffix
foreach (self::$pluralMap as $map) {
$suffix = $map[0];
$suffixLength = $map[1];
$j = 0;
// Compare characters in the plural table and of the suffix of the
// given plural one by one
while ($suffix[$j] === $lowerPluralRev[$j]) {
// Let $j point to the next character
++$j;
// Successfully compared the last character
// Add an entry with the singular suffix to the singular array
if ($j === $suffixLength) {
// Is there any character preceding the suffix in the plural string?
if ($j < $pluralLength) {
$nextIsVocal = false !== strpos('aeiou', $lowerPluralRev[$j]);
if (!$map[2] && $nextIsVocal) {
// suffix may not succeed a vocal but next char is one
break;
}
if (!$map[3] && !$nextIsVocal) {
// suffix may not succeed a consonant but next char is one
break;
}
}
$newBase = substr($plural, 0, $pluralLength - $suffixLength);
$newSuffix = $map[4];
// Check whether the first character in the plural suffix
// is uppercased. If yes, uppercase the first character in
// the singular suffix too
$firstUpper = ctype_upper($pluralRev[$j - 1]);
if (\is_array($newSuffix)) {
$singulars = [];
foreach ($newSuffix as $newSuffixEntry) {
$singulars[] = $newBase.($firstUpper ? ucfirst($newSuffixEntry) : $newSuffixEntry);
}
return $singulars;
}
return [$newBase.($firstUpper ? ucfirst($newSuffix) : $newSuffix)];
}
// Suffix is longer than word
if ($j === $pluralLength) {
break;
}
}
}
// Assume that plural and singular is identical
return [$plural];
}
/**
* {@inheritdoc}
*/
public function pluralize(string $singular): array
{
$singularRev = strrev($singular);
$lowerSingularRev = strtolower($singularRev);
$singularLength = \strlen($lowerSingularRev);
// Check if the word is one which is not inflected, return early if so
if (\in_array($lowerSingularRev, self::$uninflected, true)) {
return [$singular];
}
// The outer loop iterates over the entries of the singular table
// The inner loop $j iterates over the characters of the singular suffix
// in the singular table to compare them with the characters of the actual
// given singular suffix
foreach (self::$singularMap as $map) {
$suffix = $map[0];
$suffixLength = $map[1];
$j = 0;
// Compare characters in the singular table and of the suffix of the
// given plural one by one
while ($suffix[$j] === $lowerSingularRev[$j]) {
// Let $j point to the next character
++$j;
// Successfully compared the last character
// Add an entry with the plural suffix to the plural array
if ($j === $suffixLength) {
// Is there any character preceding the suffix in the plural string?
if ($j < $singularLength) {
$nextIsVocal = false !== strpos('aeiou', $lowerSingularRev[$j]);
if (!$map[2] && $nextIsVocal) {
// suffix may not succeed a vocal but next char is one
break;
}
if (!$map[3] && !$nextIsVocal) {
// suffix may not succeed a consonant but next char is one
break;
}
}
$newBase = substr($singular, 0, $singularLength - $suffixLength);
$newSuffix = $map[4];
// Check whether the first character in the singular suffix
// is uppercased. If yes, uppercase the first character in
// the singular suffix too
$firstUpper = ctype_upper($singularRev[$j - 1]);
if (\is_array($newSuffix)) {
$plurals = [];
foreach ($newSuffix as $newSuffixEntry) {
$plurals[] = $newBase.($firstUpper ? ucfirst($newSuffixEntry) : $newSuffixEntry);
}
return $plurals;
}
return [$newBase.($firstUpper ? ucfirst($newSuffix) : $newSuffix)];
}
// Suffix is longer than word
if ($j === $singularLength) {
break;
}
}
}
// Assume that plural is singular with a trailing `s`
return [$singular.'s'];
}
}
Copyright (c) 2019-2020 Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\String;
use Symfony\Component\String\Exception\ExceptionInterface;
use Symfony\Component\String\Exception\InvalidArgumentException;
use Symfony\Component\String\Exception\RuntimeException;
/**
* Represents a string of abstract Unicode characters.
*
* Unicode defines 3 types of "characters" (bytes, code points and grapheme clusters).
* This class is the abstract type to use as a type-hint when the logic you want to
* implement is Unicode-aware but doesn't care about code points vs grapheme clusters.
*
* @author Nicolas Grekas <p@tchwork.com>
*
* @throws ExceptionInterface
*/
abstract class AbstractUnicodeString extends AbstractString
{
public const NFC = \Normalizer::NFC;
public const NFD = \Normalizer::NFD;
public const NFKC = \Normalizer::NFKC;
public const NFKD = \Normalizer::NFKD;
// all ASCII letters sorted by typical frequency of occurrence
private const ASCII = "\x20\x65\x69\x61\x73\x6E\x74\x72\x6F\x6C\x75\x64\x5D\x5B\x63\x6D\x70\x27\x0A\x67\x7C\x68\x76\x2E\x66\x62\x2C\x3A\x3D\x2D\x71\x31\x30\x43\x32\x2A\x79\x78\x29\x28\x4C\x39\x41\x53\x2F\x50\x22\x45\x6A\x4D\x49\x6B\x33\x3E\x35\x54\x3C\x44\x34\x7D\x42\x7B\x38\x46\x77\x52\x36\x37\x55\x47\x4E\x3B\x4A\x7A\x56\x23\x48\x4F\x57\x5F\x26\x21\x4B\x3F\x58\x51\x25\x59\x5C\x09\x5A\x2B\x7E\x5E\x24\x40\x60\x7F\x00\x01\x02\x03\x04\x05\x06\x07\x08\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F";
// the subset of folded case mappings that is not in lower case mappings
private const FOLD_FROM = ['İ', 'µ', 'ſ', "\xCD\x85", 'ς', 'ϐ', 'ϑ', 'ϕ', 'ϖ', 'ϰ', 'ϱ', 'ϵ', 'ẛ', "\xE1\xBE\xBE", 'ß', 'İ', 'ʼn', 'ǰ', 'ΐ', 'ΰ', 'և', 'ẖ', 'ẗ', 'ẘ', 'ẙ', 'ẚ', 'ẞ', 'ὐ', 'ὒ', 'ὔ', 'ὖ', 'ᾀ', 'ᾁ', 'ᾂ', 'ᾃ', 'ᾄ', 'ᾅ', 'ᾆ', 'ᾇ', 'ᾈ', 'ᾉ', 'ᾊ', 'ᾋ', 'ᾌ', 'ᾍ', 'ᾎ', 'ᾏ', 'ᾐ', 'ᾑ', 'ᾒ', 'ᾓ', 'ᾔ', 'ᾕ', 'ᾖ', 'ᾗ', 'ᾘ', 'ᾙ', 'ᾚ', 'ᾛ', 'ᾜ', 'ᾝ', 'ᾞ', 'ᾟ', 'ᾠ', 'ᾡ', 'ᾢ', 'ᾣ', 'ᾤ', 'ᾥ', 'ᾦ', 'ᾧ', 'ᾨ', 'ᾩ', 'ᾪ', 'ᾫ', 'ᾬ', 'ᾭ', 'ᾮ', 'ᾯ', 'ᾲ', 'ᾳ', 'ᾴ', 'ᾶ', 'ᾷ', 'ᾼ', 'ῂ', 'ῃ', 'ῄ', 'ῆ', 'ῇ', 'ῌ', 'ῒ', 'ΐ', 'ῖ', 'ῗ', 'ῢ', 'ΰ', 'ῤ', 'ῦ', 'ῧ', 'ῲ', 'ῳ', 'ῴ', 'ῶ', 'ῷ', 'ῼ', 'ff', 'fi', 'fl', 'ffi', 'ffl', 'ſt', 'st', 'ﬓ', 'ﬔ', 'ﬕ', 'ﬖ', 'ﬗ'];
private const FOLD_TO = ['i̇', 'μ', 's', 'ι', 'σ', 'β', 'θ', 'φ', 'π', 'κ', 'ρ', 'ε', 'ṡ', 'ι', 'ss', 'i̇', 'ʼn', 'ǰ', 'ΐ', 'ΰ', 'եւ', 'ẖ', 'ẗ', 'ẘ', 'ẙ', 'aʾ', 'ss', 'ὐ', 'ὒ', 'ὔ', 'ὖ', 'ἀι', 'ἁι', 'ἂι', 'ἃι', 'ἄι', 'ἅι', 'ἆι', 'ἇι', 'ἀι', 'ἁι', 'ἂι', 'ἃι', 'ἄι', 'ἅι', 'ἆι', 'ἇι', 'ἠι', 'ἡι', 'ἢι', 'ἣι', 'ἤι', 'ἥι', 'ἦι', 'ἧι', 'ἠι', 'ἡι', 'ἢι', 'ἣι', 'ἤι', 'ἥι', 'ἦι', 'ἧι', 'ὠι', 'ὡι', 'ὢι', 'ὣι', 'ὤι', 'ὥι', 'ὦι', 'ὧι', 'ὠι', 'ὡι', 'ὢι', 'ὣι', 'ὤι', 'ὥι', 'ὦι', 'ὧι', 'ὰι', 'αι', 'άι', 'ᾶ', 'ᾶι', 'αι', 'ὴι', 'ηι', 'ήι', 'ῆ', 'ῆι', 'ηι', 'ῒ', 'ΐ', 'ῖ', 'ῗ', 'ῢ', 'ΰ', 'ῤ', 'ῦ', 'ῧ', 'ὼι', 'ωι', 'ώι', 'ῶ', 'ῶι', 'ωι', 'ff', 'fi', 'fl', 'ffi', 'ffl', 'st', 'st', 'մն', 'մե', 'մի', 'վն', 'մխ'];
// the subset of upper case mappings that map one code point to many code points
private const UPPER_FROM = ['ß', 'ff', 'fi', 'fl', 'ffi', 'ffl', 'ſt', 'st', 'և', 'ﬓ', 'ﬔ', 'ﬕ', 'ﬖ', 'ﬗ', 'ʼn', 'ΐ', 'ΰ', 'ǰ', 'ẖ', 'ẗ', 'ẘ', 'ẙ', 'ẚ', 'ὐ', 'ὒ', 'ὔ', 'ὖ', 'ᾶ', 'ῆ', 'ῒ', 'ΐ', 'ῖ', 'ῗ', 'ῢ', 'ΰ', 'ῤ', 'ῦ', 'ῧ', 'ῶ'];
private const UPPER_TO = ['SS', 'FF', 'FI', 'FL', 'FFI', 'FFL', 'ST', 'ST', 'ԵՒ', 'ՄՆ', 'ՄԵ', 'ՄԻ', 'ՎՆ', 'ՄԽ', 'ʼN', 'Ϊ́', 'Ϋ́', 'J̌', 'H̱', 'T̈', 'W̊', 'Y̊', 'Aʾ', 'Υ̓', 'Υ̓̀', 'Υ̓́', 'Υ̓͂', 'Α͂', 'Η͂', 'Ϊ̀', 'Ϊ́', 'Ι͂', 'Ϊ͂', 'Ϋ̀', 'Ϋ́', 'Ρ̓', 'Υ͂', 'Ϋ͂', 'Ω͂'];
// the subset of https://github.com/unicode-org/cldr/blob/master/common/transforms/Latin-ASCII.xml that is not in NFKD
private const TRANSLIT_FROM = ['Æ', 'Ð', 'Ø', 'Þ', 'ß', 'æ', 'ð', 'ø', 'þ', 'Đ', 'đ', 'Ħ', 'ħ', 'ı', 'ĸ', 'Ŀ', 'ŀ', 'Ł', 'ł', 'ʼn', 'Ŋ', 'ŋ', 'Œ', 'œ', 'Ŧ', 'ŧ', 'ƀ', 'Ɓ', 'Ƃ', 'ƃ', 'Ƈ', 'ƈ', 'Ɖ', 'Ɗ', 'Ƌ', 'ƌ', 'Ɛ', 'Ƒ', 'ƒ', 'Ɠ', 'ƕ', 'Ɩ', 'Ɨ', 'Ƙ', 'ƙ', 'ƚ', 'Ɲ', 'ƞ', 'Ƣ', 'ƣ', 'Ƥ', 'ƥ', 'ƫ', 'Ƭ', 'ƭ', 'Ʈ', 'Ʋ', 'Ƴ', 'ƴ', 'Ƶ', 'ƶ', 'DŽ', 'Dž', 'dž', 'Ǥ', 'ǥ', 'ȡ', 'Ȥ', 'ȥ', 'ȴ', 'ȵ', 'ȶ', 'ȷ', 'ȸ', 'ȹ', 'Ⱥ', 'Ȼ', 'ȼ', 'Ƚ', 'Ⱦ', 'ȿ', 'ɀ', 'Ƀ', 'Ʉ', 'Ɇ', 'ɇ', 'Ɉ', 'ɉ', 'Ɍ', 'ɍ', 'Ɏ', 'ɏ', 'ɓ', 'ɕ', 'ɖ', 'ɗ', 'ɛ', 'ɟ', 'ɠ', 'ɡ', 'ɢ', 'ɦ', 'ɧ', 'ɨ', 'ɪ', 'ɫ', 'ɬ', 'ɭ', 'ɱ', 'ɲ', 'ɳ', 'ɴ', 'ɶ', 'ɼ', 'ɽ', 'ɾ', 'ʀ', 'ʂ', 'ʈ', 'ʉ', 'ʋ', 'ʏ', 'ʐ', 'ʑ', 'ʙ', 'ʛ', 'ʜ', 'ʝ', 'ʟ', 'ʠ', 'ʣ', 'ʥ', 'ʦ', 'ʪ', 'ʫ', 'ᴀ', 'ᴁ', 'ᴃ', '', 'ᴅ', 'ᴆ', 'ᴇ', 'ᴊ', 'ᴋ', 'ᴌ', 'ᴍ', '', 'ᴘ', 'ᴛ', '', '', '', '', 'ᵫ', 'ᵬ', 'ᵭ', 'ᵮ', 'ᵯ', 'ᵰ', 'ᵱ', 'ᵲ', 'ᵳ', 'ᵴ', 'ᵵ', 'ᵶ', 'ᵺ', 'ᵻ', 'ᵽ', 'ᵾ', 'ᶀ', 'ᶁ', 'ᶂ', '', 'ᶄ', 'ᶅ', 'ᶆ', 'ᶇ', 'ᶈ', 'ᶉ', 'ᶊ', '', 'ᶍ', 'ᶎ', 'ᶏ', 'ᶑ', 'ᶒ', 'ᶓ', 'ᶖ', 'ᶙ', 'ẚ', 'ẜ', '', 'ẞ', 'Ỻ', 'ỻ', 'Ỽ', 'ỽ', 'Ỿ', 'ỿ', '©', '®', '₠', '₢', '₣', '₤', '₧', '₺', '₹', '', '℞', '㎧', '㎮', '㏆', '㏗', '㏞', '㏟', '¼', '½', '¾', '⅓', '⅔', '⅕', '⅖', '⅗', '⅘', '⅙', '⅚', '⅛', '⅜', '⅝', '⅞', '⅟', '', '', '', '', '', '“', '”', '„', '‟', '', '″', '〝', '〞', '«', '»', '', '', '', '', '', '', '—', '―', '︱', '︲', '', '‖', '', '⁅', '⁆', '', '、', '。', '〈', '〉', '《', '》', '', '', '〘', '〙', '〚', '〛', '︑', '︒', '︹', '︺', '︽', '︾', '︿', '﹀', '﹑', '﹝', '﹞', '⦅', '⦆', '。', '、', '×', '÷', '', '', '', '', '∥', '≪', '≫', '⦅', '⦆'];
private const TRANSLIT_TO = ['AE', 'D', 'O', 'TH', 'ss', 'ae', 'd', 'o', 'th', 'D', 'd', 'H', 'h', 'i', 'q', 'L', 'l', 'L', 'l', '\'n', 'N', 'n', 'OE', 'oe', 'T', 't', 'b', 'B', 'B', 'b', 'C', 'c', 'D', 'D', 'D', 'd', 'E', 'F', 'f', 'G', 'hv', 'I', 'I', 'K', 'k', 'l', 'N', 'n', 'OI', 'oi', 'P', 'p', 't', 'T', 't', 'T', 'V', 'Y', 'y', 'Z', 'z', 'DZ', 'Dz', 'dz', 'G', 'g', 'd', 'Z', 'z', 'l', 'n', 't', 'j', 'db', 'qp', 'A', 'C', 'c', 'L', 'T', 's', 'z', 'B', 'U', 'E', 'e', 'J', 'j', 'R', 'r', 'Y', 'y', 'b', 'c', 'd', 'd', 'e', 'j', 'g', 'g', 'G', 'h', 'h', 'i', 'I', 'l', 'l', 'l', 'm', 'n', 'n', 'N', 'OE', 'r', 'r', 'r', 'R', 's', 't', 'u', 'v', 'Y', 'z', 'z', 'B', 'G', 'H', 'j', 'L', 'q', 'dz', 'dz', 'ts', 'ls', 'lz', 'A', 'AE', 'B', 'C', 'D', 'D', 'E', 'J', 'K', 'L', 'M', 'O', 'P', 'T', 'U', 'V', 'W', 'Z', 'ue', 'b', 'd', 'f', 'm', 'n', 'p', 'r', 'r', 's', 't', 'z', 'th', 'I', 'p', 'U', 'b', 'd', 'f', 'g', 'k', 'l', 'm', 'n', 'p', 'r', 's', 'v', 'x', 'z', 'a', 'd', 'e', 'e', 'i', 'u', 'a', 's', 's', 'SS', 'LL', 'll', 'V', 'v', 'Y', 'y', '(C)', '(R)', 'CE', 'Cr', 'Fr.', 'L.', 'Pts', 'TL', 'Rs', 'x', 'Rx', 'm/s', 'rad/s', 'C/kg', 'pH', 'V/m', 'A/m', ' 1/4', ' 1/2', ' 3/4', ' 1/3', ' 2/3', ' 1/5', ' 2/5', ' 3/5', ' 4/5', ' 1/6', ' 5/6', ' 1/8', ' 3/8', ' 5/8', ' 7/8', ' 1/', '0', '\'', '\'', ',', '\'', '"', '"', ',,', '"', '\'', '"', '"', '"', '<<', '>>', '<', '>', '-', '-', '-', '-', '-', '-', '-', '-', '-', '||', '/', '[', ']', '*', ',', '.', '<', '>', '<<', '>>', '[', ']', '[', ']', '[', ']', ',', '.', '[', ']', '<<', '>>', '<', '>', ',', '[', ']', '((', '))', '.', ',', '*', '/', '-', '/', '\\', '|', '||', '<<', '>>', '((', '))'];
private static $transliterators = [];
/**
* @return static
*/
public static function fromCodePoints(int ...$codes): self
{
$string = '';
foreach ($codes as $code) {
if (0x80 > $code %= 0x200000) {
$string .= \chr($code);
} elseif (0x800 > $code) {
$string .= \chr(0xC0 | $code >> 6).\chr(0x80 | $code & 0x3F);
} elseif (0x10000 > $code) {
$string .= \chr(0xE0 | $code >> 12).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F);
} else {
$string .= \chr(0xF0 | $code >> 18).\chr(0x80 | $code >> 12 & 0x3F).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F);
}
}
return new static($string);
}
/**
* Generic UTF-8 to ASCII transliteration.
*
* Install the intl extension for best results.
*
* @param string[]|\Transliterator[]|\Closure[] $rules See "*-Latin" rules from Transliterator::listIDs()
*/
public function ascii(array $rules = []): self
{
$str = clone $this;
$s = $str->string;
$str->string = '';
array_unshift($rules, 'nfd');
$rules[] = 'latin-ascii';
if (\function_exists('transliterator_transliterate')) {
$rules[] = 'any-latin/bgn';
}
$rules[] = 'nfkd';
$rules[] = '[:nonspacing mark:] remove';
while (\strlen($s) - 1 > $i = strspn($s, self::ASCII)) {
if (0 < --$i) {
$str->string .= substr($s, 0, $i);
$s = substr($s, $i);
}
if (!$rule = array_shift($rules)) {
$rules = []; // An empty rule interrupts the next ones
}
if ($rule instanceof \Transliterator) {
$s = $rule->transliterate($s);
} elseif ($rule instanceof \Closure) {
$s = $rule($s);
} elseif ($rule) {
if ('nfd' === $rule = strtolower($rule)) {
normalizer_is_normalized($s, self::NFD) ?: $s = normalizer_normalize($s, self::NFD);
} elseif ('nfkd' === $rule) {
normalizer_is_normalized($s, self::NFKD) ?: $s = normalizer_normalize($s, self::NFKD);
} elseif ('[:nonspacing mark:] remove' === $rule) {
$s = preg_replace('/\p{Mn}++/u', '', $s);
} elseif ('latin-ascii' === $rule) {
$s = str_replace(self::TRANSLIT_FROM, self::TRANSLIT_TO, $s);
} elseif ('de-ascii' === $rule) {
$s = preg_replace("/([AUO])\u{0308}(?=\p{Ll})/u", '$1e', $s);
$s = str_replace(["a\u{0308}", "o\u{0308}", "u\u{0308}", "A\u{0308}", "O\u{0308}", "U\u{0308}"], ['ae', 'oe', 'ue', 'AE', 'OE', 'UE'], $s);
} elseif (\function_exists('transliterator_transliterate')) {
if (null === $transliterator = self::$transliterators[$rule] ?? self::$transliterators[$rule] = \Transliterator::create($rule)) {
if ('any-latin/bgn' === $rule) {
$rule = 'any-latin';
$transliterator = self::$transliterators[$rule] ?? self::$transliterators[$rule] = \Transliterator::create($rule);
}
if (null === $transliterator) {
throw new InvalidArgumentException(sprintf('Unknown transliteration rule "%s".', $rule));
}
self::$transliterators['any-latin/bgn'] = $transliterator;
}
$s = $transliterator->transliterate($s);
}
} elseif (!\function_exists('iconv')) {
$s = preg_replace('/[^\x00-\x7F]/u', '?', $s);
} else {
$s = @preg_replace_callback('/[^\x00-\x7F]/u', static function ($c) {
$c = (string) iconv('UTF-8', 'ASCII//TRANSLIT', $c[0]);
if ('' === $c && '' === iconv('UTF-8', 'ASCII//TRANSLIT', '²')) {
throw new \LogicException(sprintf('"%s" requires a translit-able iconv implementation, try installing "gnu-libiconv" if you\'re using Alpine Linux.', static::class));
}
return 1 < \strlen($c) ? ltrim($c, '\'`"^~') : ('' !== $c ? $c : '?');
}, $s);
}
}
$str->string .= $s;
return $str;
}
public function camel(): parent
{
$str = clone $this;
$str->string = str_replace(' ', '', preg_replace_callback('/\b./u', static function ($m) use (&$i) {
return 1 === ++$i ? ('İ' === $m[0] ? 'i̇' : mb_strtolower($m[0], 'UTF-8')) : mb_convert_case($m[0], \MB_CASE_TITLE, 'UTF-8');
}, preg_replace('/[^\pL0-9]++/u', ' ', $this->string)));
return $str;
}
/**
* @return int[]
*/
public function codePointsAt(int $offset): array
{
$str = $this->slice($offset, 1);
if ('' === $str->string) {
return [];
}
$codePoints = [];
foreach (preg_split('//u', $str->string, -1, \PREG_SPLIT_NO_EMPTY) as $c) {
$codePoints[] = mb_ord($c, 'UTF-8');
}
return $codePoints;
}
public function folded(bool $compat = true): parent
{
$str = clone $this;
if (!$compat || \PHP_VERSION_ID < 70300 || !\defined('Normalizer::NFKC_CF')) {
$str->string = normalizer_normalize($str->string, $compat ? \Normalizer::NFKC : \Normalizer::NFC);
$str->string = mb_strtolower(str_replace(self::FOLD_FROM, self::FOLD_TO, $this->string), 'UTF-8');
} else {
$str->string = normalizer_normalize($str->string, \Normalizer::NFKC_CF);
}
return $str;
}
public function join(array $strings, string $lastGlue = null): parent
{
$str = clone $this;
$tail = null !== $lastGlue && 1 < \count($strings) ? $lastGlue.array_pop($strings) : '';
$str->string = implode($this->string, $strings).$tail;
if (!preg_match('//u', $str->string)) {
throw new InvalidArgumentException('Invalid UTF-8 string.');
}
return $str;
}
public function lower(): parent
{
$str = clone $this;
$str->string = mb_strtolower(str_replace('İ', 'i̇', $str->string), 'UTF-8');
return $str;
}
public function match(string $regexp, int $flags = 0, int $offset = 0): array
{
$match = ((\PREG_PATTERN_ORDER | \PREG_SET_ORDER) & $flags) ? 'preg_match_all' : 'preg_match';
if ($this->ignoreCase) {
$regexp .= 'i';
}
set_error_handler(static function ($t, $m) { throw new InvalidArgumentException($m); });
try {
if (false === $match($regexp.'u', $this->string, $matches, $flags | \PREG_UNMATCHED_AS_NULL, $offset)) {
$lastError = preg_last_error();
foreach (get_defined_constants(true)['pcre'] as $k => $v) {
if ($lastError === $v && '_ERROR' === substr($k, -6)) {
throw new RuntimeException('Matching failed with '.$k.'.');
}
}
throw new RuntimeException('Matching failed with unknown error code.');
}
} finally {
restore_error_handler();
}
return $matches;
}
/**
* @return static
*/
public function normalize(int $form = self::NFC): self
{
if (!\in_array($form, [self::NFC, self::NFD, self::NFKC, self::NFKD])) {
throw new InvalidArgumentException('Unsupported normalization form.');
}
$str = clone $this;
normalizer_is_normalized($str->string, $form) ?: $str->string = normalizer_normalize($str->string, $form);
return $str;
}
public function padBoth(int $length, string $padStr = ' '): parent
{
if ('' === $padStr || !preg_match('//u', $padStr)) {
throw new InvalidArgumentException('Invalid UTF-8 string.');
}
$pad = clone $this;
$pad->string = $padStr;
return $this->pad($length, $pad, \STR_PAD_BOTH);
}
public function padEnd(int $length, string $padStr = ' '): parent
{
if ('' === $padStr || !preg_match('//u', $padStr)) {
throw new InvalidArgumentException('Invalid UTF-8 string.');
}
$pad = clone $this;
$pad->string = $padStr;
return $this->pad($length, $pad, \STR_PAD_RIGHT);
}
public function padStart(int $length, string $padStr = ' '): parent
{
if ('' === $padStr || !preg_match('//u', $padStr)) {
throw new InvalidArgumentException('Invalid UTF-8 string.');
}
$pad = clone $this;
$pad->string = $padStr;
return $this->pad($length, $pad, \STR_PAD_LEFT);
}
public function replaceMatches(string $fromRegexp, $to): parent
{
if ($this->ignoreCase) {
$fromRegexp .= 'i';
}
if (\is_array($to) || $to instanceof \Closure) {
if (!\is_callable($to)) {
throw new \TypeError(sprintf('Argument 2 passed to "%s::replaceMatches()" must be callable, array given.', static::class));
}
$replace = 'preg_replace_callback';
$to = static function (array $m) use ($to): string {
$to = $to($m);
if ('' !== $to && (!\is_string($to) || !preg_match('//u', $to))) {
throw new InvalidArgumentException('Replace callback must return a valid UTF-8 string.');
}
return $to;
};
} elseif ('' !== $to && !preg_match('//u', $to)) {
throw new InvalidArgumentException('Invalid UTF-8 string.');
} else {
$replace = 'preg_replace';
}
set_error_handler(static function ($t, $m) { throw new InvalidArgumentException($m); });
try {
if (null === $string = $replace($fromRegexp.'u', $to, $this->string)) {
$lastError = preg_last_error();
foreach (get_defined_constants(true)['pcre'] as $k => $v) {
if ($lastError === $v && '_ERROR' === substr($k, -6)) {
throw new RuntimeException('Matching failed with '.$k.'.');
}
}
throw new RuntimeException('Matching failed with unknown error code.');
}
} finally {
restore_error_handler();
}
$str = clone $this;
$str->string = $string;
return $str;
}
public function reverse(): parent
{
$str = clone $this;
$str->string = implode('', array_reverse(preg_split('/(\X)/u', $str->string, -1, \PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY)));
return $str;
}
public function snake(): parent
{
$str = $this->camel()->title();
$str->string = mb_strtolower(preg_replace(['/(\p{Lu}+)(\p{Lu}\p{Ll})/u', '/([\p{Ll}0-9])(\p{Lu})/u'], '\1_\2', $str->string), 'UTF-8');
return $str;
}
public function title(bool $allWords = false): parent
{
$str = clone $this;
$limit = $allWords ? -1 : 1;
$str->string = preg_replace_callback('/\b./u', static function (array $m): string {
return mb_convert_case($m[0], \MB_CASE_TITLE, 'UTF-8');
}, $str->string, $limit);
return $str;
}
public function trim(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}"): parent
{
if (" \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}" !== $chars && !preg_match('//u', $chars)) {
throw new InvalidArgumentException('Invalid UTF-8 chars.');
}
$chars = preg_quote($chars);
$str = clone $this;
$str->string = preg_replace("{^[$chars]++|[$chars]++$}uD", '', $str->string);
return $str;
}
public function trimEnd(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}"): parent
{
if (" \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}" !== $chars && !preg_match('//u', $chars)) {
throw new InvalidArgumentException('Invalid UTF-8 chars.');
}
$chars = preg_quote($chars);
$str = clone $this;
$str->string = preg_replace("{[$chars]++$}uD", '', $str->string);
return $str;
}
public function trimStart(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}"): parent
{
if (" \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}" !== $chars && !preg_match('//u', $chars)) {
throw new InvalidArgumentException('Invalid UTF-8 chars.');
}
$chars = preg_quote($chars);
$str = clone $this;
$str->string = preg_replace("{^[$chars]++}uD", '', $str->string);
return $str;
}
public function upper(): parent
{
$str = clone $this;
$str->string = mb_strtoupper($str->string, 'UTF-8');
if (\PHP_VERSION_ID < 70300) {
$str->string = str_replace(self::UPPER_FROM, self::UPPER_TO, $str->string);
}
return $str;
}
public function width(bool $ignoreAnsiDecoration = true): int
{
$width = 0;
$s = str_replace(["\x00", "\x05", "\x07"], '', $this->string);
if (false !== strpos($s, "\r")) {
$s = str_replace(["\r\n", "\r"], "\n", $s);
}
if (!$ignoreAnsiDecoration) {
$s = preg_replace('/[\p{Cc}\x7F]++/u', '', $s);
}
foreach (explode("\n", $s) as $s) {
if ($ignoreAnsiDecoration) {
$s = preg_replace('/(?:\x1B(?:
\[ [\x30-\x3F]*+ [\x20-\x2F]*+ [0x40-\x7E]
| [P\]X^_] .*? \x1B\\\\
| [\x41-\x7E]
)|[\p{Cc}\x7F]++)/xu', '', $s);
}
// Non printable characters have been dropped, so wcswidth cannot logically return -1.
$width += $this->wcswidth($s);
}
return $width;
}
/**
* @return static
*/
private function pad(int $len, self $pad, int $type): parent
{
$sLen = $this->length();
if ($len <= $sLen) {
return clone $this;
}
$padLen = $pad->length();
$freeLen = $len - $sLen;
$len = $freeLen % $padLen;
switch ($type) {
case \STR_PAD_RIGHT:
return $this->append(str_repeat($pad->string, $freeLen / $padLen).($len ? $pad->slice(0, $len) : ''));
case \STR_PAD_LEFT:
return $this->prepend(str_repeat($pad->string, $freeLen / $padLen).($len ? $pad->slice(0, $len) : ''));
case \STR_PAD_BOTH:
$freeLen /= 2;
$rightLen = ceil($freeLen);
$len = $rightLen % $padLen;
$str = $this->append(str_repeat($pad->string, $rightLen / $padLen).($len ? $pad->slice(0, $len) : ''));
$leftLen = floor($freeLen);
$len = $leftLen % $padLen;
return $str->prepend(str_repeat($pad->string, $leftLen / $padLen).($len ? $pad->slice(0, $len) : ''));
default:
throw new InvalidArgumentException('Invalid padding type.');
}
}
/**
* Based on https://github.com/jquast/wcwidth, a Python implementation of https://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c.
*/
private function wcswidth(string $string): int
{
$width = 0;
foreach (preg_split('//u', $string, -1, \PREG_SPLIT_NO_EMPTY) as $c) {
$codePoint = mb_ord($c, 'UTF-8');
if (0 === $codePoint // NULL
|| 0x034F === $codePoint // COMBINING GRAPHEME JOINER
|| (0x200B <= $codePoint && 0x200F >= $codePoint) // ZERO WIDTH SPACE to RIGHT-TO-LEFT MARK
|| 0x2028 === $codePoint // LINE SEPARATOR
|| 0x2029 === $codePoint // PARAGRAPH SEPARATOR
|| (0x202A <= $codePoint && 0x202E >= $codePoint) // LEFT-TO-RIGHT EMBEDDING to RIGHT-TO-LEFT OVERRIDE
|| (0x2060 <= $codePoint && 0x2063 >= $codePoint) // WORD JOINER to INVISIBLE SEPARATOR
) {
continue;
}
// Non printable characters
if (32 > $codePoint // C0 control characters
|| (0x07F <= $codePoint && 0x0A0 > $codePoint) // C1 control characters and DEL
) {
return -1;
}
static $tableZero;
if (null === $tableZero) {
$tableZero = require __DIR__.'/Resources/data/wcswidth_table_zero.php';
}
if ($codePoint >= $tableZero[0][0] && $codePoint <= $tableZero[$ubound = \count($tableZero) - 1][1]) {
$lbound = 0;
while ($ubound >= $lbound) {
$mid = floor(($lbound + $ubound) / 2);
if ($codePoint > $tableZero[$mid][1]) {
$lbound = $mid + 1;
} elseif ($codePoint < $tableZero[$mid][0]) {
$ubound = $mid - 1;
} else {
continue 2;
}
}
}
static $tableWide;
if (null === $tableWide) {
$tableWide = require __DIR__.'/Resources/data/wcswidth_table_wide.php';
}
if ($codePoint >= $tableWide[0][0] && $codePoint <= $tableWide[$ubound = \count($tableWide) - 1][1]) {
$lbound = 0;
while ($ubound >= $lbound) {
$mid = floor(($lbound + $ubound) / 2);
if ($codePoint > $tableWide[$mid][1]) {
$lbound = $mid + 1;
} elseif ($codePoint < $tableWide[$mid][0]) {
$ubound = $mid - 1;
} else {
$width += 2;
continue 2;
}
}
}
++$width;
}
return $width;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\String\Exception;
interface ExceptionInterface extends \Throwable
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\String\Exception;
class RuntimeException extends \RuntimeException implements ExceptionInterface
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\String\Exception;
class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\String;
use Symfony\Component\String\Exception\ExceptionInterface;
use Symfony\Component\String\Exception\InvalidArgumentException;
/**
* Represents a string of Unicode grapheme clusters encoded as UTF-8.
*
* A letter followed by combining characters (accents typically) form what Unicode defines
* as a grapheme cluster: a character as humans mean it in written texts. This class knows
* about the concept and won't split a letter apart from its combining accents. It also
* ensures all string comparisons happen on their canonically-composed representation,
* ignoring e.g. the order in which accents are listed when a letter has many of them.
*
* @see https://unicode.org/reports/tr15/
*
* @author Nicolas Grekas <p@tchwork.com>
* @author Hugo Hamon <hugohamon@neuf.fr>
*
* @throws ExceptionInterface
*/
class UnicodeString extends AbstractUnicodeString
{
public function __construct(string $string = '')
{
$this->string = normalizer_is_normalized($string) ? $string : normalizer_normalize($string);
if (false === $this->string) {
throw new InvalidArgumentException('Invalid UTF-8 string.');
}
}
public function append(string ...$suffix): AbstractString
{
$str = clone $this;
$str->string = $this->string.(1 >= \count($suffix) ? ($suffix[0] ?? '') : implode('', $suffix));
normalizer_is_normalized($str->string) ?: $str->string = normalizer_normalize($str->string);
if (false === $str->string) {
throw new InvalidArgumentException('Invalid UTF-8 string.');
}
return $str;
}
public function chunk(int $length = 1): array
{
if (1 > $length) {
throw new InvalidArgumentException('The chunk length must be greater than zero.');
}
if ('' === $this->string) {
return [];
}
$rx = '/(';
while (65535 < $length) {
$rx .= '\X{65535}';
$length -= 65535;
}
$rx .= '\X{'.$length.'})/u';
$str = clone $this;
$chunks = [];
foreach (preg_split($rx, $this->string, -1, \PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY) as $chunk) {
$str->string = $chunk;
$chunks[] = clone $str;
}
return $chunks;
}
public function endsWith($suffix): bool
{
if ($suffix instanceof AbstractString) {
$suffix = $suffix->string;
} elseif (\is_array($suffix) || $suffix instanceof \Traversable) {
return parent::endsWith($suffix);
} else {
$suffix = (string) $suffix;
}
$form = null === $this->ignoreCase ? \Normalizer::NFD : \Normalizer::NFC;
normalizer_is_normalized($suffix, $form) ?: $suffix = normalizer_normalize($suffix, $form);
if ('' === $suffix || false === $suffix) {
return false;
}
if ($this->ignoreCase) {
return 0 === mb_stripos(grapheme_extract($this->string, \strlen($suffix), \GRAPHEME_EXTR_MAXBYTES, \strlen($this->string) - \strlen($suffix)), $suffix, 0, 'UTF-8');
}
return $suffix === grapheme_extract($this->string, \strlen($suffix), \GRAPHEME_EXTR_MAXBYTES, \strlen($this->string) - \strlen($suffix));
}
public function equalsTo($string): bool
{
if ($string instanceof AbstractString) {
$string = $string->string;
} elseif (\is_array($string) || $string instanceof \Traversable) {
return parent::equalsTo($string);
} else {
$string = (string) $string;
}
$form = null === $this->ignoreCase ? \Normalizer::NFD : \Normalizer::NFC;
normalizer_is_normalized($string, $form) ?: $string = normalizer_normalize($string, $form);
if ('' !== $string && false !== $string && $this->ignoreCase) {
return \strlen($string) === \strlen($this->string) && 0 === mb_stripos($this->string, $string, 0, 'UTF-8');
}
return $string === $this->string;
}
public function indexOf($needle, int $offset = 0): ?int
{
if ($needle instanceof AbstractString) {
$needle = $needle->string;
} elseif (\is_array($needle) || $needle instanceof \Traversable) {
return parent::indexOf($needle, $offset);
} else {
$needle = (string) $needle;
}
$form = null === $this->ignoreCase ? \Normalizer::NFD : \Normalizer::NFC;
normalizer_is_normalized($needle, $form) ?: $needle = normalizer_normalize($needle, $form);
if ('' === $needle || false === $needle) {
return null;
}
try {
$i = $this->ignoreCase ? grapheme_stripos($this->string, $needle, $offset) : grapheme_strpos($this->string, $needle, $offset);
} catch (\ValueError $e) {
return null;
}
return false === $i ? null : $i;
}
public function indexOfLast($needle, int $offset = 0): ?int
{
if ($needle instanceof AbstractString) {
$needle = $needle->string;
} elseif (\is_array($needle) || $needle instanceof \Traversable) {
return parent::indexOfLast($needle, $offset);
} else {
$needle = (string) $needle;
}
$form = null === $this->ignoreCase ? \Normalizer::NFD : \Normalizer::NFC;
normalizer_is_normalized($needle, $form) ?: $needle = normalizer_normalize($needle, $form);
if ('' === $needle || false === $needle) {
return null;
}
$string = $this->string;
if (0 > $offset) {
// workaround https://bugs.php.net/74264
if (0 > $offset += grapheme_strlen($needle)) {
$string = grapheme_substr($string, 0, $offset);
}
$offset = 0;
}
$i = $this->ignoreCase ? grapheme_strripos($string, $needle, $offset) : grapheme_strrpos($string, $needle, $offset);
return false === $i ? null : $i;
}
public function join(array $strings, string $lastGlue = null): AbstractString
{
$str = parent::join($strings, $lastGlue);
normalizer_is_normalized($str->string) ?: $str->string = normalizer_normalize($str->string);
return $str;
}
public function length(): int
{
return grapheme_strlen($this->string);
}
/**
* @return static
*/
public function normalize(int $form = self::NFC): parent
{
$str = clone $this;
if (\in_array($form, [self::NFC, self::NFKC], true)) {
normalizer_is_normalized($str->string, $form) ?: $str->string = normalizer_normalize($str->string, $form);
} elseif (!\in_array($form, [self::NFD, self::NFKD], true)) {
throw new InvalidArgumentException('Unsupported normalization form.');
} elseif (!normalizer_is_normalized($str->string, $form)) {
$str->string = normalizer_normalize($str->string, $form);
$str->ignoreCase = null;
}
return $str;
}
public function prepend(string ...$prefix): AbstractString
{
$str = clone $this;
$str->string = (1 >= \count($prefix) ? ($prefix[0] ?? '') : implode('', $prefix)).$this->string;
normalizer_is_normalized($str->string) ?: $str->string = normalizer_normalize($str->string);
if (false === $str->string) {
throw new InvalidArgumentException('Invalid UTF-8 string.');
}
return $str;
}
public function replace(string $from, string $to): AbstractString
{
$str = clone $this;
normalizer_is_normalized($from) ?: $from = normalizer_normalize($from);
if ('' !== $from && false !== $from) {
$tail = $str->string;
$result = '';
$indexOf = $this->ignoreCase ? 'grapheme_stripos' : 'grapheme_strpos';
while ('' !== $tail && false !== $i = $indexOf($tail, $from)) {
$slice = grapheme_substr($tail, 0, $i);
$result .= $slice.$to;
$tail = substr($tail, \strlen($slice) + \strlen($from));
}
$str->string = $result.$tail;
normalizer_is_normalized($str->string) ?: $str->string = normalizer_normalize($str->string);
if (false === $str->string) {
throw new InvalidArgumentException('Invalid UTF-8 string.');
}
}
return $str;
}
public function replaceMatches(string $fromRegexp, $to): AbstractString
{
$str = parent::replaceMatches($fromRegexp, $to);
normalizer_is_normalized($str->string) ?: $str->string = normalizer_normalize($str->string);
return $str;
}
public function slice(int $start = 0, int $length = null): AbstractString
{
$str = clone $this;
if (\PHP_VERSION_ID < 80000 && 0 > $start && grapheme_strlen($this->string) < -$start) {
$start = 0;
}
$str->string = (string) grapheme_substr($this->string, $start, $length ?? 2147483647);
return $str;
}
public function splice(string $replacement, int $start = 0, int $length = null): AbstractString
{
$str = clone $this;
if (\PHP_VERSION_ID < 80000 && 0 > $start && grapheme_strlen($this->string) < -$start) {
$start = 0;
}
$start = $start ? \strlen(grapheme_substr($this->string, 0, $start)) : 0;
$length = $length ? \strlen(grapheme_substr($this->string, $start, $length ?? 2147483647)) : $length;
$str->string = substr_replace($this->string, $replacement, $start, $length ?? 2147483647);
normalizer_is_normalized($str->string) ?: $str->string = normalizer_normalize($str->string);
if (false === $str->string) {
throw new InvalidArgumentException('Invalid UTF-8 string.');
}
return $str;
}
public function split(string $delimiter, int $limit = null, int $flags = null): array
{
if (1 > $limit = $limit ?? 2147483647) {
throw new InvalidArgumentException('Split limit must be a positive integer.');
}
if ('' === $delimiter) {
throw new InvalidArgumentException('Split delimiter is empty.');
}
if (null !== $flags) {
return parent::split($delimiter.'u', $limit, $flags);
}
normalizer_is_normalized($delimiter) ?: $delimiter = normalizer_normalize($delimiter);
if (false === $delimiter) {
throw new InvalidArgumentException('Split delimiter is not a valid UTF-8 string.');
}
$str = clone $this;
$tail = $this->string;
$chunks = [];
$indexOf = $this->ignoreCase ? 'grapheme_stripos' : 'grapheme_strpos';
while (1 < $limit && false !== $i = $indexOf($tail, $delimiter)) {
$str->string = grapheme_substr($tail, 0, $i);
$chunks[] = clone $str;
$tail = substr($tail, \strlen($str->string) + \strlen($delimiter));
--$limit;
}
$str->string = $tail;
$chunks[] = clone $str;
return $chunks;
}
public function startsWith($prefix): bool
{
if ($prefix instanceof AbstractString) {
$prefix = $prefix->string;
} elseif (\is_array($prefix) || $prefix instanceof \Traversable) {
return parent::startsWith($prefix);
} else {
$prefix = (string) $prefix;
}
$form = null === $this->ignoreCase ? \Normalizer::NFD : \Normalizer::NFC;
normalizer_is_normalized($prefix, $form) ?: $prefix = normalizer_normalize($prefix, $form);
if ('' === $prefix || false === $prefix) {
return false;
}
if ($this->ignoreCase) {
return 0 === mb_stripos(grapheme_extract($this->string, \strlen($prefix), \GRAPHEME_EXTR_MAXBYTES), $prefix, 0, 'UTF-8');
}
return $prefix === grapheme_extract($this->string, \strlen($prefix), \GRAPHEME_EXTR_MAXBYTES);
}
public function __wakeup()
{
normalizer_is_normalized($this->string) ?: $this->string = normalizer_normalize($this->string);
}
public function __clone()
{
if (null === $this->ignoreCase) {
normalizer_is_normalized($this->string) ?: $this->string = normalizer_normalize($this->string);
}
$this->ignoreCase = false;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\String;
use Symfony\Component\String\Exception\ExceptionInterface;
use Symfony\Component\String\Exception\InvalidArgumentException;
use Symfony\Component\String\Exception\RuntimeException;
/**
* Represents a string of abstract characters.
*
* Unicode defines 3 types of "characters" (bytes, code points and grapheme clusters).
* This class is the abstract type to use as a type-hint when the logic you want to
* implement doesn't care about the exact variant it deals with.
*
* @author Nicolas Grekas <p@tchwork.com>
* @author Hugo Hamon <hugohamon@neuf.fr>
*
* @throws ExceptionInterface
*/
abstract class AbstractString implements \Stringable, \JsonSerializable
{
public const PREG_PATTERN_ORDER = \PREG_PATTERN_ORDER;
public const PREG_SET_ORDER = \PREG_SET_ORDER;
public const PREG_OFFSET_CAPTURE = \PREG_OFFSET_CAPTURE;
public const PREG_UNMATCHED_AS_NULL = \PREG_UNMATCHED_AS_NULL;
public const PREG_SPLIT = 0;
public const PREG_SPLIT_NO_EMPTY = \PREG_SPLIT_NO_EMPTY;
public const PREG_SPLIT_DELIM_CAPTURE = \PREG_SPLIT_DELIM_CAPTURE;
public const PREG_SPLIT_OFFSET_CAPTURE = \PREG_SPLIT_OFFSET_CAPTURE;
protected $string = '';
protected $ignoreCase = false;
abstract public function __construct(string $string = '');
/**
* Unwraps instances of AbstractString back to strings.
*
* @return string[]|array
*/
public static function unwrap(array $values): array
{
foreach ($values as $k => $v) {
if ($v instanceof self) {
$values[$k] = $v->__toString();
} elseif (\is_array($v) && $values[$k] !== $v = static::unwrap($v)) {
$values[$k] = $v;
}
}
return $values;
}
/**
* Wraps (and normalizes) strings in instances of AbstractString.
*
* @return static[]|array
*/
public static function wrap(array $values): array
{
$i = 0;
$keys = null;
foreach ($values as $k => $v) {
if (\is_string($k) && '' !== $k && $k !== $j = (string) new static($k)) {
$keys = $keys ?? array_keys($values);
$keys[$i] = $j;
}
if (\is_string($v)) {
$values[$k] = new static($v);
} elseif (\is_array($v) && $values[$k] !== $v = static::wrap($v)) {
$values[$k] = $v;
}
++$i;
}
return null !== $keys ? array_combine($keys, $values) : $values;
}
/**
* @param string|string[] $needle
*
* @return static
*/
public function after($needle, bool $includeNeedle = false, int $offset = 0): self
{
$str = clone $this;
$i = \PHP_INT_MAX;
foreach ((array) $needle as $n) {
$n = (string) $n;
$j = $this->indexOf($n, $offset);
if (null !== $j && $j < $i) {
$i = $j;
$str->string = $n;
}
}
if (\PHP_INT_MAX === $i) {
return $str;
}
if (!$includeNeedle) {
$i += $str->length();
}
return $this->slice($i);
}
/**
* @param string|string[] $needle
*
* @return static
*/
public function afterLast($needle, bool $includeNeedle = false, int $offset = 0): self
{
$str = clone $this;
$i = null;
foreach ((array) $needle as $n) {
$n = (string) $n;
$j = $this->indexOfLast($n, $offset);
if (null !== $j && $j >= $i) {
$i = $offset = $j;
$str->string = $n;
}
}
if (null === $i) {
return $str;
}
if (!$includeNeedle) {
$i += $str->length();
}
return $this->slice($i);
}
/**
* @return static
*/
abstract public function append(string ...$suffix): self;
/**
* @param string|string[] $needle
*
* @return static
*/
public function before($needle, bool $includeNeedle = false, int $offset = 0): self
{
$str = clone $this;
$i = \PHP_INT_MAX;
foreach ((array) $needle as $n) {
$n = (string) $n;
$j = $this->indexOf($n, $offset);
if (null !== $j && $j < $i) {
$i = $j;
$str->string = $n;
}
}
if (\PHP_INT_MAX === $i) {
return $str;
}
if ($includeNeedle) {
$i += $str->length();
}
return $this->slice(0, $i);
}
/**
* @param string|string[] $needle
*
* @return static
*/
public function beforeLast($needle, bool $includeNeedle = false, int $offset = 0): self
{
$str = clone $this;
$i = null;
foreach ((array) $needle as $n) {
$n = (string) $n;
$j = $this->indexOfLast($n, $offset);
if (null !== $j && $j >= $i) {
$i = $offset = $j;
$str->string = $n;
}
}
if (null === $i) {
return $str;
}
if ($includeNeedle) {
$i += $str->length();
}
return $this->slice(0, $i);
}
/**
* @return int[]
*/
public function bytesAt(int $offset): array
{
$str = $this->slice($offset, 1);
return '' === $str->string ? [] : array_values(unpack('C*', $str->string));
}
/**
* @return static
*/
abstract public function camel(): self;
/**
* @return static[]
*/
abstract public function chunk(int $length = 1): array;
/**
* @return static
*/
public function collapseWhitespace(): self
{
$str = clone $this;
$str->string = trim(preg_replace('/(?:\s{2,}+|[^\S ])/', ' ', $str->string));
return $str;
}
/**
* @param string|string[] $needle
*/
public function containsAny($needle): bool
{
return null !== $this->indexOf($needle);
}
/**
* @param string|string[] $suffix
*/
public function endsWith($suffix): bool
{
if (!\is_array($suffix) && !$suffix instanceof \Traversable) {
throw new \TypeError(sprintf('Method "%s()" must be overridden by class "%s" to deal with non-iterable values.', __FUNCTION__, static::class));
}
foreach ($suffix as $s) {
if ($this->endsWith((string) $s)) {
return true;
}
}
return false;
}
/**
* @return static
*/
public function ensureEnd(string $suffix): self
{
if (!$this->endsWith($suffix)) {
return $this->append($suffix);
}
$suffix = preg_quote($suffix);
$regex = '{('.$suffix.')(?:'.$suffix.')++$}D';
return $this->replaceMatches($regex.($this->ignoreCase ? 'i' : ''), '$1');
}
/**
* @return static
*/
public function ensureStart(string $prefix): self
{
$prefix = new static($prefix);
if (!$this->startsWith($prefix)) {
return $this->prepend($prefix);
}
$str = clone $this;
$i = $prefixLen = $prefix->length();
while ($this->indexOf($prefix, $i) === $i) {
$str = $str->slice($prefixLen);
$i += $prefixLen;
}
return $str;
}
/**
* @param string|string[] $string
*/
public function equalsTo($string): bool
{
if (!\is_array($string) && !$string instanceof \Traversable) {
throw new \TypeError(sprintf('Method "%s()" must be overridden by class "%s" to deal with non-iterable values.', __FUNCTION__, static::class));
}
foreach ($string as $s) {
if ($this->equalsTo((string) $s)) {
return true;
}
}
return false;
}
/**
* @return static
*/
abstract public function folded(): self;
/**
* @return static
*/
public function ignoreCase(): self
{
$str = clone $this;
$str->ignoreCase = true;
return $str;
}
/**
* @param string|string[] $needle
*/
public function indexOf($needle, int $offset = 0): ?int
{
if (!\is_array($needle) && !$needle instanceof \Traversable) {
throw new \TypeError(sprintf('Method "%s()" must be overridden by class "%s" to deal with non-iterable values.', __FUNCTION__, static::class));
}
$i = \PHP_INT_MAX;
foreach ($needle as $n) {
$j = $this->indexOf((string) $n, $offset);
if (null !== $j && $j < $i) {
$i = $j;
}
}
return \PHP_INT_MAX === $i ? null : $i;
}
/**
* @param string|string[] $needle
*/
public function indexOfLast($needle, int $offset = 0): ?int
{
if (!\is_array($needle) && !$needle instanceof \Traversable) {
throw new \TypeError(sprintf('Method "%s()" must be overridden by class "%s" to deal with non-iterable values.', __FUNCTION__, static::class));
}
$i = null;
foreach ($needle as $n) {
$j = $this->indexOfLast((string) $n, $offset);
if (null !== $j && $j >= $i) {
$i = $offset = $j;
}
}
return $i;
}
public function isEmpty(): bool
{
return '' === $this->string;
}
/**
* @return static
*/
abstract public function join(array $strings, string $lastGlue = null): self;
public function jsonSerialize(): string
{
return $this->string;
}
abstract public function length(): int;
/**
* @return static
*/
abstract public function lower(): self;
/**
* Matches the string using a regular expression.
*
* Pass PREG_PATTERN_ORDER or PREG_SET_ORDER as $flags to get all occurrences matching the regular expression.
*
* @return array All matches in a multi-dimensional array ordered according to flags
*/
abstract public function match(string $regexp, int $flags = 0, int $offset = 0): array;
/**
* @return static
*/
abstract public function padBoth(int $length, string $padStr = ' '): self;
/**
* @return static
*/
abstract public function padEnd(int $length, string $padStr = ' '): self;
/**
* @return static
*/
abstract public function padStart(int $length, string $padStr = ' '): self;
/**
* @return static
*/
abstract public function prepend(string ...$prefix): self;
/**
* @return static
*/
public function repeat(int $multiplier): self
{
if (0 > $multiplier) {
throw new InvalidArgumentException(sprintf('Multiplier must be positive, %d given.', $multiplier));
}
$str = clone $this;
$str->string = str_repeat($str->string, $multiplier);
return $str;
}
/**
* @return static
*/
abstract public function replace(string $from, string $to): self;
/**
* @param string|callable $to
*
* @return static
*/
abstract public function replaceMatches(string $fromRegexp, $to): self;
/**
* @return static
*/
abstract public function reverse(): self;
/**
* @return static
*/
abstract public function slice(int $start = 0, int $length = null): self;
/**
* @return static
*/
abstract public function snake(): self;
/**
* @return static
*/
abstract public function splice(string $replacement, int $start = 0, int $length = null): self;
/**
* @return static[]
*/
public function split(string $delimiter, int $limit = null, int $flags = null): array
{
if (null === $flags) {
throw new \TypeError('Split behavior when $flags is null must be implemented by child classes.');
}
if ($this->ignoreCase) {
$delimiter .= 'i';
}
set_error_handler(static function ($t, $m) { throw new InvalidArgumentException($m); });
try {
if (false === $chunks = preg_split($delimiter, $this->string, $limit, $flags)) {
$lastError = preg_last_error();
foreach (get_defined_constants(true)['pcre'] as $k => $v) {
if ($lastError === $v && '_ERROR' === substr($k, -6)) {
throw new RuntimeException('Splitting failed with '.$k.'.');
}
}
throw new RuntimeException('Splitting failed with unknown error code.');
}
} finally {
restore_error_handler();
}
$str = clone $this;
if (self::PREG_SPLIT_OFFSET_CAPTURE & $flags) {
foreach ($chunks as &$chunk) {
$str->string = $chunk[0];
$chunk[0] = clone $str;
}
} else {
foreach ($chunks as &$chunk) {
$str->string = $chunk;
$chunk = clone $str;
}
}
return $chunks;
}
/**
* @param string|string[] $prefix
*/
public function startsWith($prefix): bool
{
if (!\is_array($prefix) && !$prefix instanceof \Traversable) {
throw new \TypeError(sprintf('Method "%s()" must be overridden by class "%s" to deal with non-iterable values.', __FUNCTION__, static::class));
}
foreach ($prefix as $prefix) {
if ($this->startsWith((string) $prefix)) {
return true;
}
}
return false;
}
/**
* @return static
*/
abstract public function title(bool $allWords = false): self;
public function toByteString(string $toEncoding = null): ByteString
{
$b = new ByteString();
$toEncoding = \in_array($toEncoding, ['utf8', 'utf-8', 'UTF8'], true) ? 'UTF-8' : $toEncoding;
if (null === $toEncoding || $toEncoding === $fromEncoding = $this instanceof AbstractUnicodeString || preg_match('//u', $b->string) ? 'UTF-8' : 'Windows-1252') {
$b->string = $this->string;
return $b;
}
set_error_handler(static function ($t, $m) { throw new InvalidArgumentException($m); });
try {
try {
$b->string = mb_convert_encoding($this->string, $toEncoding, 'UTF-8');
} catch (InvalidArgumentException $e) {
if (!\function_exists('iconv')) {
throw $e;
}
$b->string = iconv('UTF-8', $toEncoding, $this->string);
}
} finally {
restore_error_handler();
}
return $b;
}
public function toCodePointString(): CodePointString
{
return new CodePointString($this->string);
}
public function toString(): string
{
return $this->string;
}
public function toUnicodeString(): UnicodeString
{
return new UnicodeString($this->string);
}
/**
* @return static
*/
abstract public function trim(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}"): self;
/**
* @return static
*/
abstract public function trimEnd(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}"): self;
/**
* @return static
*/
abstract public function trimStart(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}"): self;
/**
* @return static
*/
public function truncate(int $length, string $ellipsis = '', bool $cut = true): self
{
$stringLength = $this->length();
if ($stringLength <= $length) {
return clone $this;
}
$ellipsisLength = '' !== $ellipsis ? (new static($ellipsis))->length() : 0;
if ($length < $ellipsisLength) {
$ellipsisLength = 0;
}
if (!$cut) {
if (null === $length = $this->indexOf([' ', "\r", "\n", "\t"], ($length ?: 1) - 1)) {
return clone $this;
}
$length += $ellipsisLength;
}
$str = $this->slice(0, $length - $ellipsisLength);
return $ellipsisLength ? $str->trimEnd()->append($ellipsis) : $str;
}
/**
* @return static
*/
abstract public function upper(): self;
/**
* Returns the printable length on a terminal.
*/
abstract public function width(bool $ignoreAnsiDecoration = true): int;
/**
* @return static
*/
public function wordwrap(int $width = 75, string $break = "\n", bool $cut = false): self
{
$lines = '' !== $break ? $this->split($break) : [clone $this];
$chars = [];
$mask = '';
if (1 === \count($lines) && '' === $lines[0]->string) {
return $lines[0];
}
foreach ($lines as $i => $line) {
if ($i) {
$chars[] = $break;
$mask .= '#';
}
foreach ($line->chunk() as $char) {
$chars[] = $char->string;
$mask .= ' ' === $char->string ? ' ' : '?';
}
}
$string = '';
$j = 0;
$b = $i = -1;
$mask = wordwrap($mask, $width, '#', $cut);
while (false !== $b = strpos($mask, '#', $b + 1)) {
for (++$i; $i < $b; ++$i) {
$string .= $chars[$j];
unset($chars[$j++]);
}
if ($break === $chars[$j] || ' ' === $chars[$j]) {
unset($chars[$j++]);
}
$string .= $break;
}
$str = clone $this;
$str->string = $string.implode('', $chars);
return $str;
}
public function __sleep(): array
{
return ['string'];
}
public function __clone()
{
$this->ignoreCase = false;
}
public function __toString(): string
{
return $this->string;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\String\Slugger;
use Symfony\Component\String\AbstractUnicodeString;
use Symfony\Component\String\UnicodeString;
use Symfony\Contracts\Translation\LocaleAwareInterface;
if (!interface_exists(LocaleAwareInterface::class)) {
throw new \LogicException('You cannot use the "Symfony\Component\String\Slugger\AsciiSlugger" as the "symfony/translation-contracts" package is not installed. Try running "composer require symfony/translation-contracts".');
}
/**
* @author Titouan Galopin <galopintitouan@gmail.com>
*/
class AsciiSlugger implements SluggerInterface, LocaleAwareInterface
{
private const LOCALE_TO_TRANSLITERATOR_ID = [
'am' => 'Amharic-Latin',
'ar' => 'Arabic-Latin',
'az' => 'Azerbaijani-Latin',
'be' => 'Belarusian-Latin',
'bg' => 'Bulgarian-Latin',
'bn' => 'Bengali-Latin',
'de' => 'de-ASCII',
'el' => 'Greek-Latin',
'fa' => 'Persian-Latin',
'he' => 'Hebrew-Latin',
'hy' => 'Armenian-Latin',
'ka' => 'Georgian-Latin',
'kk' => 'Kazakh-Latin',
'ky' => 'Kirghiz-Latin',
'ko' => 'Korean-Latin',
'mk' => 'Macedonian-Latin',
'mn' => 'Mongolian-Latin',
'or' => 'Oriya-Latin',
'ps' => 'Pashto-Latin',
'ru' => 'Russian-Latin',
'sr' => 'Serbian-Latin',
'sr_Cyrl' => 'Serbian-Latin',
'th' => 'Thai-Latin',
'tk' => 'Turkmen-Latin',
'uk' => 'Ukrainian-Latin',
'uz' => 'Uzbek-Latin',
'zh' => 'Han-Latin',
];
private $defaultLocale;
private $symbolsMap = [
'en' => ['@' => 'at', '&' => 'and'],
];
/**
* Cache of transliterators per locale.
*
* @var \Transliterator[]
*/
private $transliterators = [];
/**
* @param array|\Closure|null $symbolsMap
*/
public function __construct(string $defaultLocale = null, $symbolsMap = null)
{
if (null !== $symbolsMap && !\is_array($symbolsMap) && !$symbolsMap instanceof \Closure) {
throw new \TypeError(sprintf('Argument 2 passed to "%s()" must be array, Closure or null, "%s" given.', __METHOD__, \gettype($symbolsMap)));
}
$this->defaultLocale = $defaultLocale;
$this->symbolsMap = $symbolsMap ?? $this->symbolsMap;
}
/**
* {@inheritdoc}
*/
public function setLocale($locale)
{
$this->defaultLocale = $locale;
}
/**
* {@inheritdoc}
*/
public function getLocale()
{
return $this->defaultLocale;
}
/**
* {@inheritdoc}
*/
public function slug(string $string, string $separator = '-', string $locale = null): AbstractUnicodeString
{
$locale = $locale ?? $this->defaultLocale;
$transliterator = [];
if ('de' === $locale || 0 === strpos($locale, 'de_')) {
// Use the shortcut for German in UnicodeString::ascii() if possible (faster and no requirement on intl)
$transliterator = ['de-ASCII'];
} elseif (\function_exists('transliterator_transliterate') && $locale) {
$transliterator = (array) $this->createTransliterator($locale);
}
if ($this->symbolsMap instanceof \Closure) {
$symbolsMap = $this->symbolsMap;
array_unshift($transliterator, static function ($s) use ($symbolsMap, $locale) {
return $symbolsMap($s, $locale);
});
}
$unicodeString = (new UnicodeString($string))->ascii($transliterator);
if (\is_array($this->symbolsMap) && isset($this->symbolsMap[$locale])) {
foreach ($this->symbolsMap[$locale] as $char => $replace) {
$unicodeString = $unicodeString->replace($char, ' '.$replace.' ');
}
}
return $unicodeString
->replaceMatches('/[^A-Za-z0-9]++/', $separator)
->trim($separator)
;
}
private function createTransliterator(string $locale): ?\Transliterator
{
if (\array_key_exists($locale, $this->transliterators)) {
return $this->transliterators[$locale];
}
// Exact locale supported, cache and return
if ($id = self::LOCALE_TO_TRANSLITERATOR_ID[$locale] ?? null) {
return $this->transliterators[$locale] = \Transliterator::create($id.'/BGN') ?? \Transliterator::create($id);
}
// Locale not supported and no parent, fallback to any-latin
if (false === $str = strrchr($locale, '_')) {
return $this->transliterators[$locale] = null;
}
// Try to use the parent locale (ie. try "de" for "de_AT") and cache both locales
$parent = substr($locale, 0, -\strlen($str));
if ($id = self::LOCALE_TO_TRANSLITERATOR_ID[$parent] ?? null) {
$transliterator = \Transliterator::create($id.'/BGN') ?? \Transliterator::create($id);
}
return $this->transliterators[$locale] = $this->transliterators[$parent] = $transliterator ?? null;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\String\Slugger;
use Symfony\Component\String\AbstractUnicodeString;
/**
* Creates a URL-friendly slug from a given string.
*
* @author Titouan Galopin <galopintitouan@gmail.com>
*/
interface SluggerInterface
{
/**
* Creates a slug for the given string and locale, using appropriate transliteration when needed.
*/
public function slug(string $string, string $separator = '-', string $locale = null): AbstractUnicodeString;
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\String;
use Symfony\Component\String\Exception\ExceptionInterface;
use Symfony\Component\String\Exception\InvalidArgumentException;
use Symfony\Component\String\Exception\RuntimeException;
/**
* Represents a binary-safe string of bytes.
*
* @author Nicolas Grekas <p@tchwork.com>
* @author Hugo Hamon <hugohamon@neuf.fr>
*
* @throws ExceptionInterface
*/
class ByteString extends AbstractString
{
private const ALPHABET_ALPHANUMERIC = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
public function __construct(string $string = '')
{
$this->string = $string;
}
/*
* The following method was derived from code of the Hack Standard Library (v4.40 - 2020-05-03)
*
* https://github.com/hhvm/hsl/blob/80a42c02f036f72a42f0415e80d6b847f4bf62d5/src/random/private.php#L16
*
* Code subject to the MIT license (https://github.com/hhvm/hsl/blob/master/LICENSE).
*
* Copyright (c) 2004-2020, Facebook, Inc. (https://www.facebook.com/)
*/
public static function fromRandom(int $length = 16, string $alphabet = null): self
{
if ($length <= 0) {
throw new InvalidArgumentException(sprintf('A strictly positive length is expected, "%d" given.', $length));
}
$alphabet = $alphabet ?? self::ALPHABET_ALPHANUMERIC;
$alphabetSize = \strlen($alphabet);
$bits = (int) ceil(log($alphabetSize, 2.0));
if ($bits <= 0 || $bits > 56) {
throw new InvalidArgumentException('The length of the alphabet must in the [2^1, 2^56] range.');
}
$ret = '';
while ($length > 0) {
$urandomLength = (int) ceil(2 * $length * $bits / 8.0);
$data = random_bytes($urandomLength);
$unpackedData = 0;
$unpackedBits = 0;
for ($i = 0; $i < $urandomLength && $length > 0; ++$i) {
// Unpack 8 bits
$unpackedData = ($unpackedData << 8) | \ord($data[$i]);
$unpackedBits += 8;
// While we have enough bits to select a character from the alphabet, keep
// consuming the random data
for (; $unpackedBits >= $bits && $length > 0; $unpackedBits -= $bits) {
$index = ($unpackedData & ((1 << $bits) - 1));
$unpackedData >>= $bits;
// Unfortunately, the alphabet size is not necessarily a power of two.
// Worst case, it is 2^k + 1, which means we need (k+1) bits and we
// have around a 50% chance of missing as k gets larger
if ($index < $alphabetSize) {
$ret .= $alphabet[$index];
--$length;
}
}
}
}
return new static($ret);
}
public function bytesAt(int $offset): array
{
$str = $this->string[$offset] ?? '';
return '' === $str ? [] : [\ord($str)];
}
public function append(string ...$suffix): parent
{
$str = clone $this;
$str->string .= 1 >= \count($suffix) ? ($suffix[0] ?? '') : implode('', $suffix);
return $str;
}
public function camel(): parent
{
$str = clone $this;
$str->string = lcfirst(str_replace(' ', '', ucwords(preg_replace('/[^a-zA-Z0-9\x7f-\xff]++/', ' ', $this->string))));
return $str;
}
public function chunk(int $length = 1): array
{
if (1 > $length) {
throw new InvalidArgumentException('The chunk length must be greater than zero.');
}
if ('' === $this->string) {
return [];
}
$str = clone $this;
$chunks = [];
foreach (str_split($this->string, $length) as $chunk) {
$str->string = $chunk;
$chunks[] = clone $str;
}
return $chunks;
}
public function endsWith($suffix): bool
{
if ($suffix instanceof parent) {
$suffix = $suffix->string;
} elseif (\is_array($suffix) || $suffix instanceof \Traversable) {
return parent::endsWith($suffix);
} else {
$suffix = (string) $suffix;
}
return '' !== $suffix && \strlen($this->string) >= \strlen($suffix) && 0 === substr_compare($this->string, $suffix, -\strlen($suffix), null, $this->ignoreCase);
}
public function equalsTo($string): bool
{
if ($string instanceof parent) {
$string = $string->string;
} elseif (\is_array($string) || $string instanceof \Traversable) {
return parent::equalsTo($string);
} else {
$string = (string) $string;
}
if ('' !== $string && $this->ignoreCase) {
return 0 === strcasecmp($string, $this->string);
}
return $string === $this->string;
}
public function folded(): parent
{
$str = clone $this;
$str->string = strtolower($str->string);
return $str;
}
public function indexOf($needle, int $offset = 0): ?int
{
if ($needle instanceof parent) {
$needle = $needle->string;
} elseif (\is_array($needle) || $needle instanceof \Traversable) {
return parent::indexOf($needle, $offset);
} else {
$needle = (string) $needle;
}
if ('' === $needle) {
return null;
}
$i = $this->ignoreCase ? stripos($this->string, $needle, $offset) : strpos($this->string, $needle, $offset);
return false === $i ? null : $i;
}
public function indexOfLast($needle, int $offset = 0): ?int
{
if ($needle instanceof parent) {
$needle = $needle->string;
} elseif (\is_array($needle) || $needle instanceof \Traversable) {
return parent::indexOfLast($needle, $offset);
} else {
$needle = (string) $needle;
}
if ('' === $needle) {
return null;
}
$i = $this->ignoreCase ? strripos($this->string, $needle, $offset) : strrpos($this->string, $needle, $offset);
return false === $i ? null : $i;
}
public function isUtf8(): bool
{
return '' === $this->string || preg_match('//u', $this->string);
}
public function join(array $strings, string $lastGlue = null): parent
{
$str = clone $this;
$tail = null !== $lastGlue && 1 < \count($strings) ? $lastGlue.array_pop($strings) : '';
$str->string = implode($this->string, $strings).$tail;
return $str;
}
public function length(): int
{
return \strlen($this->string);
}
public function lower(): parent
{
$str = clone $this;
$str->string = strtolower($str->string);
return $str;
}
public function match(string $regexp, int $flags = 0, int $offset = 0): array
{
$match = ((\PREG_PATTERN_ORDER | \PREG_SET_ORDER) & $flags) ? 'preg_match_all' : 'preg_match';
if ($this->ignoreCase) {
$regexp .= 'i';
}
set_error_handler(static function ($t, $m) { throw new InvalidArgumentException($m); });
try {
if (false === $match($regexp, $this->string, $matches, $flags | \PREG_UNMATCHED_AS_NULL, $offset)) {
$lastError = preg_last_error();
foreach (get_defined_constants(true)['pcre'] as $k => $v) {
if ($lastError === $v && '_ERROR' === substr($k, -6)) {
throw new RuntimeException('Matching failed with '.$k.'.');
}
}
throw new RuntimeException('Matching failed with unknown error code.');
}
} finally {
restore_error_handler();
}
return $matches;
}
public function padBoth(int $length, string $padStr = ' '): parent
{
$str = clone $this;
$str->string = str_pad($this->string, $length, $padStr, \STR_PAD_BOTH);
return $str;
}
public function padEnd(int $length, string $padStr = ' '): parent
{
$str = clone $this;
$str->string = str_pad($this->string, $length, $padStr, \STR_PAD_RIGHT);
return $str;
}
public function padStart(int $length, string $padStr = ' '): parent
{
$str = clone $this;
$str->string = str_pad($this->string, $length, $padStr, \STR_PAD_LEFT);
return $str;
}
public function prepend(string ...$prefix): parent
{
$str = clone $this;
$str->string = (1 >= \count($prefix) ? ($prefix[0] ?? '') : implode('', $prefix)).$str->string;
return $str;
}
public function replace(string $from, string $to): parent
{
$str = clone $this;
if ('' !== $from) {
$str->string = $this->ignoreCase ? str_ireplace($from, $to, $this->string) : str_replace($from, $to, $this->string);
}
return $str;
}
public function replaceMatches(string $fromRegexp, $to): parent
{
if ($this->ignoreCase) {
$fromRegexp .= 'i';
}
if (\is_array($to)) {
if (!\is_callable($to)) {
throw new \TypeError(sprintf('Argument 2 passed to "%s::replaceMatches()" must be callable, array given.', static::class));
}
$replace = 'preg_replace_callback';
} else {
$replace = $to instanceof \Closure ? 'preg_replace_callback' : 'preg_replace';
}
set_error_handler(static function ($t, $m) { throw new InvalidArgumentException($m); });
try {
if (null === $string = $replace($fromRegexp, $to, $this->string)) {
$lastError = preg_last_error();
foreach (get_defined_constants(true)['pcre'] as $k => $v) {
if ($lastError === $v && '_ERROR' === substr($k, -6)) {
throw new RuntimeException('Matching failed with '.$k.'.');
}
}
throw new RuntimeException('Matching failed with unknown error code.');
}
} finally {
restore_error_handler();
}
$str = clone $this;
$str->string = $string;
return $str;
}
public function reverse(): parent
{
$str = clone $this;
$str->string = strrev($str->string);
return $str;
}
public function slice(int $start = 0, int $length = null): parent
{
$str = clone $this;
$str->string = (string) substr($this->string, $start, $length ?? \PHP_INT_MAX);
return $str;
}
public function snake(): parent
{
$str = $this->camel()->title();
$str->string = strtolower(preg_replace(['/([A-Z]+)([A-Z][a-z])/', '/([a-z\d])([A-Z])/'], '\1_\2', $str->string));
return $str;
}
public function splice(string $replacement, int $start = 0, int $length = null): parent
{
$str = clone $this;
$str->string = substr_replace($this->string, $replacement, $start, $length ?? \PHP_INT_MAX);
return $str;
}
public function split(string $delimiter, int $limit = null, int $flags = null): array
{
if (1 > $limit = $limit ?? \PHP_INT_MAX) {
throw new InvalidArgumentException('Split limit must be a positive integer.');
}
if ('' === $delimiter) {
throw new InvalidArgumentException('Split delimiter is empty.');
}
if (null !== $flags) {
return parent::split($delimiter, $limit, $flags);
}
$str = clone $this;
$chunks = $this->ignoreCase
? preg_split('{'.preg_quote($delimiter).'}iD', $this->string, $limit)
: explode($delimiter, $this->string, $limit);
foreach ($chunks as &$chunk) {
$str->string = $chunk;
$chunk = clone $str;
}
return $chunks;
}
public function startsWith($prefix): bool
{
if ($prefix instanceof parent) {
$prefix = $prefix->string;
} elseif (!\is_string($prefix)) {
return parent::startsWith($prefix);
}
return '' !== $prefix && 0 === ($this->ignoreCase ? strncasecmp($this->string, $prefix, \strlen($prefix)) : strncmp($this->string, $prefix, \strlen($prefix)));
}
public function title(bool $allWords = false): parent
{
$str = clone $this;
$str->string = $allWords ? ucwords($str->string) : ucfirst($str->string);
return $str;
}
public function toUnicodeString(string $fromEncoding = null): UnicodeString
{
return new UnicodeString($this->toCodePointString($fromEncoding)->string);
}
public function toCodePointString(string $fromEncoding = null): CodePointString
{
$u = new CodePointString();
if (\in_array($fromEncoding, [null, 'utf8', 'utf-8', 'UTF8', 'UTF-8'], true) && preg_match('//u', $this->string)) {
$u->string = $this->string;
return $u;
}
set_error_handler(static function ($t, $m) { throw new InvalidArgumentException($m); });
try {
try {
$validEncoding = false !== mb_detect_encoding($this->string, $fromEncoding ?? 'Windows-1252', true);
} catch (InvalidArgumentException $e) {
if (!\function_exists('iconv')) {
throw $e;
}
$u->string = iconv($fromEncoding ?? 'Windows-1252', 'UTF-8', $this->string);
return $u;
}
} finally {
restore_error_handler();
}
if (!$validEncoding) {
throw new InvalidArgumentException(sprintf('Invalid "%s" string.', $fromEncoding ?? 'Windows-1252'));
}
$u->string = mb_convert_encoding($this->string, 'UTF-8', $fromEncoding ?? 'Windows-1252');
return $u;
}
public function trim(string $chars = " \t\n\r\0\x0B\x0C"): parent
{
$str = clone $this;
$str->string = trim($str->string, $chars);
return $str;
}
public function trimEnd(string $chars = " \t\n\r\0\x0B\x0C"): parent
{
$str = clone $this;
$str->string = rtrim($str->string, $chars);
return $str;
}
public function trimStart(string $chars = " \t\n\r\0\x0B\x0C"): parent
{
$str = clone $this;
$str->string = ltrim($str->string, $chars);
return $str;
}
public function upper(): parent
{
$str = clone $this;
$str->string = strtoupper($str->string);
return $str;
}
public function width(bool $ignoreAnsiDecoration = true): int
{
$string = preg_match('//u', $this->string) ? $this->string : preg_replace('/[\x80-\xFF]/', '?', $this->string);
return (new CodePointString($string))->width($ignoreAnsiDecoration);
}
}
<?php
/*
* This file has been auto-generated by the Symfony String Component for internal use.
*
* Unicode version: 13.0.0
* Date: 2020-03-12T08:04:34+00:00
*/
return [
[
768,
879,
],
[
1155,
1159,
],
[
1160,
1161,
],
[
1425,
1469,
],
[
1471,
1471,
],
[
1473,
1474,
],
[
1476,
1477,
],
[
1479,
1479,
],
[
1552,
1562,
],
[
1611,
1631,
],
[
1648,
1648,
],
[
1750,
1756,
],
[
1759,
1764,
],
[
1767,
1768,
],
[
1770,
1773,
],
[
1809,
1809,
],
[
1840,
1866,
],
[
1958,
1968,
],
[
2027,
2035,
],
[
2045,
2045,
],
[
2070,
2073,
],
[
2075,
2083,
],
[
2085,
2087,
],
[
2089,
2093,
],
[
2137,
2139,
],
[
2259,
2273,
],
[
2275,
2306,
],
[
2362,
2362,
],
[
2364,
2364,
],
[
2369,
2376,
],
[
2381,
2381,
],
[
2385,
2391,
],
[
2402,
2403,
],
[
2433,
2433,
],
[
2492,
2492,
],
[
2497,
2500,
],
[
2509,
2509,
],
[
2530,
2531,
],
[
2558,
2558,
],
[
2561,
2562,
],
[
2620,
2620,
],
[
2625,
2626,
],
[
2631,
2632,
],
[
2635,
2637,
],
[
2641,
2641,
],
[
2672,
2673,
],
[
2677,
2677,
],
[
2689,
2690,
],
[
2748,
2748,
],
[
2753,
2757,
],
[
2759,
2760,
],
[
2765,
2765,
],
[
2786,
2787,
],
[
2810,
2815,
],
[
2817,
2817,
],
[
2876,
2876,
],
[
2879,
2879,
],
[
2881,
2884,
],
[
2893,
2893,
],
[
2901,
2902,
],
[
2914,
2915,
],
[
2946,
2946,
],
[
3008,
3008,
],
[
3021,
3021,
],
[
3072,
3072,
],
[
3076,
3076,
],
[
3134,
3136,
],
[
3142,
3144,
],
[
3146,
3149,
],
[
3157,
3158,
],
[
3170,
3171,
],
[
3201,
3201,
],
[
3260,
3260,
],
[
3263,
3263,
],
[
3270,
3270,
],
[
3276,
3277,
],
[
3298,
3299,
],
[
3328,
3329,
],
[
3387,
3388,
],
[
3393,
3396,
],
[
3405,
3405,
],
[
3426,
3427,
],
[
3457,
3457,
],
[
3530,
3530,
],
[
3538,
3540,
],
[
3542,
3542,
],
[
3633,
3633,
],
[
3636,
3642,
],
[
3655,
3662,
],
[
3761,
3761,
],
[
3764,
3772,
],
[
3784,
3789,
],
[
3864,
3865,
],
[
3893,
3893,
],
[
3895,
3895,
],
[
3897,
3897,
],
[
3953,
3966,
],
[
3968,
3972,
],
[
3974,
3975,
],
[
3981,
3991,
],
[
3993,
4028,
],
[
4038,
4038,
],
[
4141,
4144,
],
[
4146,
4151,
],
[
4153,
4154,
],
[
4157,
4158,
],
[
4184,
4185,
],
[
4190,
4192,
],
[
4209,
4212,
],
[
4226,
4226,
],
[
4229,
4230,
],
[
4237,
4237,
],
[
4253,
4253,
],
[
4957,
4959,
],
[
5906,
5908,
],
[
5938,
5940,
],
[
5970,
5971,
],
[
6002,
6003,
],
[
6068,
6069,
],
[
6071,
6077,
],
[
6086,
6086,
],
[
6089,
6099,
],
[
6109,
6109,
],
[
6155,
6157,
],
[
6277,
6278,
],
[
6313,
6313,
],
[
6432,
6434,
],
[
6439,
6440,
],
[
6450,
6450,
],
[
6457,
6459,
],
[
6679,
6680,
],
[
6683,
6683,
],
[
6742,
6742,
],
[
6744,
6750,
],
[
6752,
6752,
],
[
6754,
6754,
],
[
6757,
6764,
],
[
6771,
6780,
],
[
6783,
6783,
],
[
6832,
6845,
],
[
6846,
6846,
],
[
6847,
6848,
],
[
6912,
6915,
],
[
6964,
6964,
],
[
6966,
6970,
],
[
6972,
6972,
],
[
6978,
6978,
],
[
7019,
7027,
],
[
7040,
7041,
],
[
7074,
7077,
],
[
7080,
7081,
],
[
7083,
7085,
],
[
7142,
7142,
],
[
7144,
7145,
],
[
7149,
7149,
],
[
7151,
7153,
],
[
7212,
7219,
],
[
7222,
7223,
],
[
7376,
7378,
],
[
7380,
7392,
],
[
7394,
7400,
],
[
7405,
7405,
],
[
7412,
7412,
],
[
7416,
7417,
],
[
7616,
7673,
],
[
7675,
7679,
],
[
8400,
8412,
],
[
8413,
8416,
],
[
8417,
8417,
],
[
8418,
8420,
],
[
8421,
8432,
],
[
11503,
11505,
],
[
11647,
11647,
],
[
11744,
11775,
],
[
12330,
12333,
],
[
12441,
12442,
],
[
42607,
42607,
],
[
42608,
42610,
],
[
42612,
42621,
],
[
42654,
42655,
],
[
42736,
42737,
],
[
43010,
43010,
],
[
43014,
43014,
],
[
43019,
43019,
],
[
43045,
43046,
],
[
43052,
43052,
],
[
43204,
43205,
],
[
43232,
43249,
],
[
43263,
43263,
],
[
43302,
43309,
],
[
43335,
43345,
],
[
43392,
43394,
],
[
43443,
43443,
],
[
43446,
43449,
],
[
43452,
43453,
],
[
43493,
43493,
],
[
43561,
43566,
],
[
43569,
43570,
],
[
43573,
43574,
],
[
43587,
43587,
],
[
43596,
43596,
],
[
43644,
43644,
],
[
43696,
43696,
],
[
43698,
43700,
],
[
43703,
43704,
],
[
43710,
43711,
],
[
43713,
43713,
],
[
43756,
43757,
],
[
43766,
43766,
],
[
44005,
44005,
],
[
44008,
44008,
],
[
44013,
44013,
],
[
64286,
64286,
],
[
65024,
65039,
],
[
65056,
65071,
],
[
66045,
66045,
],
[
66272,
66272,
],
[
66422,
66426,
],
[
68097,
68099,
],
[
68101,
68102,
],
[
68108,
68111,
],
[
68152,
68154,
],
[
68159,
68159,
],
[
68325,
68326,
],
[
68900,
68903,
],
[
69291,
69292,
],
[
69446,
69456,
],
[
69633,
69633,
],
[
69688,
69702,
],
[
69759,
69761,
],
[
69811,
69814,
],
[
69817,
69818,
],
[
69888,
69890,
],
[
69927,
69931,
],
[
69933,
69940,
],
[
70003,
70003,
],
[
70016,
70017,
],
[
70070,
70078,
],
[
70089,
70092,
],
[
70095,
70095,
],
[
70191,
70193,
],
[
70196,
70196,
],
[
70198,
70199,
],
[
70206,
70206,
],
[
70367,
70367,
],
[
70371,
70378,
],
[
70400,
70401,
],
[
70459,
70460,
],
[
70464,
70464,
],
[
70502,
70508,
],
[
70512,
70516,
],
[
70712,
70719,
],
[
70722,
70724,
],
[
70726,
70726,
],
[
70750,
70750,
],
[
70835,
70840,
],
[
70842,
70842,
],
[
70847,
70848,
],
[
70850,
70851,
],
[
71090,
71093,
],
[
71100,
71101,
],
[
71103,
71104,
],
[
71132,
71133,
],
[
71219,
71226,
],
[
71229,
71229,
],
[
71231,
71232,
],
[
71339,
71339,
],
[
71341,
71341,
],
[
71344,
71349,
],
[
71351,
71351,
],
[
71453,
71455,
],
[
71458,
71461,
],
[
71463,
71467,
],
[
71727,
71735,
],
[
71737,
71738,
],
[
71995,
71996,
],
[
71998,
71998,
],
[
72003,
72003,
],
[
72148,
72151,
],
[
72154,
72155,
],
[
72160,
72160,
],
[
72193,
72202,
],
[
72243,
72248,
],
[
72251,
72254,
],
[
72263,
72263,
],
[
72273,
72278,
],
[
72281,
72283,
],
[
72330,
72342,
],
[
72344,
72345,
],
[
72752,
72758,
],
[
72760,
72765,
],
[
72767,
72767,
],
[
72850,
72871,
],
[
72874,
72880,
],
[
72882,
72883,
],
[
72885,
72886,
],
[
73009,
73014,
],
[
73018,
73018,
],
[
73020,
73021,
],
[
73023,
73029,
],
[
73031,
73031,
],
[
73104,
73105,
],
[
73109,
73109,
],
[
73111,
73111,
],
[
73459,
73460,
],
[
92912,
92916,
],
[
92976,
92982,
],
[
94031,
94031,
],
[
94095,
94098,
],
[
94180,
94180,
],
[
113821,
113822,
],
[
119143,
119145,
],
[
119163,
119170,
],
[
119173,
119179,
],
[
119210,
119213,
],
[
119362,
119364,
],
[
121344,
121398,
],
[
121403,
121452,
],
[
121461,
121461,
],
[
121476,
121476,
],
[
121499,
121503,
],
[
121505,
121519,
],
[
122880,
122886,
],
[
122888,
122904,
],
[
122907,
122913,
],
[
122915,
122916,
],
[
122918,
122922,
],
[
123184,
123190,
],
[
123628,
123631,
],
[
125136,
125142,
],
[
125252,
125258,
],
[
917760,
917999,
],
];
<?php
/*
* This file has been auto-generated by the Symfony String Component for internal use.
*
* Unicode version: 13.0.0
* Date: 2020-03-12T08:04:33+00:00
*/
return [
[
4352,
4447,
],
[
8986,
8987,
],
[
9001,
9001,
],
[
9002,
9002,
],
[
9193,
9196,
],
[
9200,
9200,
],
[
9203,
9203,
],
[
9725,
9726,
],
[
9748,
9749,
],
[
9800,
9811,
],
[
9855,
9855,
],
[
9875,
9875,
],
[
9889,
9889,
],
[
9898,
9899,
],
[
9917,
9918,
],
[
9924,
9925,
],
[
9934,
9934,
],
[
9940,
9940,
],
[
9962,
9962,
],
[
9970,
9971,
],
[
9973,
9973,
],
[
9978,
9978,
],
[
9981,
9981,
],
[
9989,
9989,
],
[
9994,
9995,
],
[
10024,
10024,
],
[
10060,
10060,
],
[
10062,
10062,
],
[
10067,
10069,
],
[
10071,
10071,
],
[
10133,
10135,
],
[
10160,
10160,
],
[
10175,
10175,
],
[
11035,
11036,
],
[
11088,
11088,
],
[
11093,
11093,
],
[
11904,
11929,
],
[
11931,
12019,
],
[
12032,
12245,
],
[
12272,
12283,
],
[
12288,
12288,
],
[
12289,
12291,
],
[
12292,
12292,
],
[
12293,
12293,
],
[
12294,
12294,
],
[
12295,
12295,
],
[
12296,
12296,
],
[
12297,
12297,
],
[
12298,
12298,
],
[
12299,
12299,
],
[
12300,
12300,
],
[
12301,
12301,
],
[
12302,
12302,
],
[
12303,
12303,
],
[
12304,
12304,
],
[
12305,
12305,
],
[
12306,
12307,
],
[
12308,
12308,
],
[
12309,
12309,
],
[
12310,
12310,
],
[
12311,
12311,
],
[
12312,
12312,
],
[
12313,
12313,
],
[
12314,
12314,
],
[
12315,
12315,
],
[
12316,
12316,
],
[
12317,
12317,
],
[
12318,
12319,
],
[
12320,
12320,
],
[
12321,
12329,
],
[
12330,
12333,
],
[
12334,
12335,
],
[
12336,
12336,
],
[
12337,
12341,
],
[
12342,
12343,
],
[
12344,
12346,
],
[
12347,
12347,
],
[
12348,
12348,
],
[
12349,
12349,
],
[
12350,
12350,
],
[
12353,
12438,
],
[
12441,
12442,
],
[
12443,
12444,
],
[
12445,
12446,
],
[
12447,
12447,
],
[
12448,
12448,
],
[
12449,
12538,
],
[
12539,
12539,
],
[
12540,
12542,
],
[
12543,
12543,
],
[
12549,
12591,
],
[
12593,
12686,
],
[
12688,
12689,
],
[
12690,
12693,
],
[
12694,
12703,
],
[
12704,
12735,
],
[
12736,
12771,
],
[
12784,
12799,
],
[
12800,
12830,
],
[
12832,
12841,
],
[
12842,
12871,
],
[
12880,
12880,
],
[
12881,
12895,
],
[
12896,
12927,
],
[
12928,
12937,
],
[
12938,
12976,
],
[
12977,
12991,
],
[
12992,
13055,
],
[
13056,
13311,
],
[
13312,
19903,
],
[
19968,
40956,
],
[
40957,
40959,
],
[
40960,
40980,
],
[
40981,
40981,
],
[
40982,
42124,
],
[
42128,
42182,
],
[
43360,
43388,
],
[
44032,
55203,
],
[
63744,
64109,
],
[
64110,
64111,
],
[
64112,
64217,
],
[
64218,
64255,
],
[
65040,
65046,
],
[
65047,
65047,
],
[
65048,
65048,
],
[
65049,
65049,
],
[
65072,
65072,
],
[
65073,
65074,
],
[
65075,
65076,
],
[
65077,
65077,
],
[
65078,
65078,
],
[
65079,
65079,
],
[
65080,
65080,
],
[
65081,
65081,
],
[
65082,
65082,
],
[
65083,
65083,
],
[
65084,
65084,
],
[
65085,
65085,
],
[
65086,
65086,
],
[
65087,
65087,
],
[
65088,
65088,
],
[
65089,
65089,
],
[
65090,
65090,
],
[
65091,
65091,
],
[
65092,
65092,
],
[
65093,
65094,
],
[
65095,
65095,
],
[
65096,
65096,
],
[
65097,
65100,
],
[
65101,
65103,
],
[
65104,
65106,
],
[
65108,
65111,
],
[
65112,
65112,
],
[
65113,
65113,
],
[
65114,
65114,
],
[
65115,
65115,
],
[
65116,
65116,
],
[
65117,
65117,
],
[
65118,
65118,
],
[
65119,
65121,
],
[
65122,
65122,
],
[
65123,
65123,
],
[
65124,
65126,
],
[
65128,
65128,
],
[
65129,
65129,
],
[
65130,
65131,
],
[
65281,
65283,
],
[
65284,
65284,
],
[
65285,
65287,
],
[
65288,
65288,
],
[
65289,
65289,
],
[
65290,
65290,
],
[
65291,
65291,
],
[
65292,
65292,
],
[
65293,
65293,
],
[
65294,
65295,
],
[
65296,
65305,
],
[
65306,
65307,
],
[
65308,
65310,
],
[
65311,
65312,
],
[
65313,
65338,
],
[
65339,
65339,
],
[
65340,
65340,
],
[
65341,
65341,
],
[
65342,
65342,
],
[
65343,
65343,
],
[
65344,
65344,
],
[
65345,
65370,
],
[
65371,
65371,
],
[
65372,
65372,
],
[
65373,
65373,
],
[
65374,
65374,
],
[
65375,
65375,
],
[
65376,
65376,
],
[
65504,
65505,
],
[
65506,
65506,
],
[
65507,
65507,
],
[
65508,
65508,
],
[
65509,
65510,
],
[
94176,
94177,
],
[
94178,
94178,
],
[
94179,
94179,
],
[
94180,
94180,
],
[
94192,
94193,
],
[
94208,
100343,
],
[
100352,
101119,
],
[
101120,
101589,
],
[
101632,
101640,
],
[
110592,
110847,
],
[
110848,
110878,
],
[
110928,
110930,
],
[
110948,
110951,
],
[
110960,
111355,
],
[
126980,
126980,
],
[
127183,
127183,
],
[
127374,
127374,
],
[
127377,
127386,
],
[
127488,
127490,
],
[
127504,
127547,
],
[
127552,
127560,
],
[
127568,
127569,
],
[
127584,
127589,
],
[
127744,
127776,
],
[
127789,
127797,
],
[
127799,
127868,
],
[
127870,
127891,
],
[
127904,
127946,
],
[
127951,
127955,
],
[
127968,
127984,
],
[
127988,
127988,
],
[
127992,
127994,
],
[
127995,
127999,
],
[
128000,
128062,
],
[
128064,
128064,
],
[
128066,
128252,
],
[
128255,
128317,
],
[
128331,
128334,
],
[
128336,
128359,
],
[
128378,
128378,
],
[
128405,
128406,
],
[
128420,
128420,
],
[
128507,
128511,
],
[
128512,
128591,
],
[
128640,
128709,
],
[
128716,
128716,
],
[
128720,
128722,
],
[
128725,
128727,
],
[
128747,
128748,
],
[
128756,
128764,
],
[
128992,
129003,
],
[
129292,
129338,
],
[
129340,
129349,
],
[
129351,
129400,
],
[
129402,
129483,
],
[
129485,
129535,
],
[
129648,
129652,
],
[
129656,
129658,
],
[
129664,
129670,
],
[
129680,
129704,
],
[
129712,
129718,
],
[
129728,
129730,
],
[
129744,
129750,
],
[
131072,
173789,
],
[
173790,
173823,
],
[
173824,
177972,
],
[
177973,
177983,
],
[
177984,
178205,
],
[
178206,
178207,
],
[
178208,
183969,
],
[
183970,
183983,
],
[
183984,
191456,
],
[
191457,
194559,
],
[
194560,
195101,
],
[
195102,
195103,
],
[
195104,
196605,
],
[
196608,
201546,
],
[
201547,
262141,
],
];
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\String;
function u(?string $string = ''): UnicodeString
{
return new UnicodeString($string ?? '');
}
function b(?string $string = ''): ByteString
{
return new ByteString($string ?? '');
}
/**
* @return UnicodeString|ByteString
*/
function s(?string $string = ''): AbstractString
{
$string = $string ?? '';
return preg_match('//u', $string) ? new UnicodeString($string) : new ByteString($string);
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\String;
use Symfony\Component\String\Exception\ExceptionInterface;
use Symfony\Component\String\Exception\InvalidArgumentException;
/**
* Represents a string of Unicode code points encoded as UTF-8.
*
* @author Nicolas Grekas <p@tchwork.com>
* @author Hugo Hamon <hugohamon@neuf.fr>
*
* @throws ExceptionInterface
*/
class CodePointString extends AbstractUnicodeString
{
public function __construct(string $string = '')
{
if ('' !== $string && !preg_match('//u', $string)) {
throw new InvalidArgumentException('Invalid UTF-8 string.');
}
$this->string = $string;
}
public function append(string ...$suffix): AbstractString
{
$str = clone $this;
$str->string .= 1 >= \count($suffix) ? ($suffix[0] ?? '') : implode('', $suffix);
if (!preg_match('//u', $str->string)) {
throw new InvalidArgumentException('Invalid UTF-8 string.');
}
return $str;
}
public function chunk(int $length = 1): array
{
if (1 > $length) {
throw new InvalidArgumentException('The chunk length must be greater than zero.');
}
if ('' === $this->string) {
return [];
}
$rx = '/(';
while (65535 < $length) {
$rx .= '.{65535}';
$length -= 65535;
}
$rx .= '.{'.$length.'})/us';
$str = clone $this;
$chunks = [];
foreach (preg_split($rx, $this->string, -1, \PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY) as $chunk) {
$str->string = $chunk;
$chunks[] = clone $str;
}
return $chunks;
}
public function codePointsAt(int $offset): array
{
$str = $offset ? $this->slice($offset, 1) : $this;
return '' === $str->string ? [] : [mb_ord($str->string, 'UTF-8')];
}
public function endsWith($suffix): bool
{
if ($suffix instanceof AbstractString) {
$suffix = $suffix->string;
} elseif (\is_array($suffix) || $suffix instanceof \Traversable) {
return parent::endsWith($suffix);
} else {
$suffix = (string) $suffix;
}
if ('' === $suffix || !preg_match('//u', $suffix)) {
return false;
}
if ($this->ignoreCase) {
return preg_match('{'.preg_quote($suffix).'$}iuD', $this->string);
}
return \strlen($this->string) >= \strlen($suffix) && 0 === substr_compare($this->string, $suffix, -\strlen($suffix));
}
public function equalsTo($string): bool
{
if ($string instanceof AbstractString) {
$string = $string->string;
} elseif (\is_array($string) || $string instanceof \Traversable) {
return parent::equalsTo($string);
} else {
$string = (string) $string;
}
if ('' !== $string && $this->ignoreCase) {
return \strlen($string) === \strlen($this->string) && 0 === mb_stripos($this->string, $string, 0, 'UTF-8');
}
return $string === $this->string;
}
public function indexOf($needle, int $offset = 0): ?int
{
if ($needle instanceof AbstractString) {
$needle = $needle->string;
} elseif (\is_array($needle) || $needle instanceof \Traversable) {
return parent::indexOf($needle, $offset);
} else {
$needle = (string) $needle;
}
if ('' === $needle) {
return null;
}
$i = $this->ignoreCase ? mb_stripos($this->string, $needle, $offset, 'UTF-8') : mb_strpos($this->string, $needle, $offset, 'UTF-8');
return false === $i ? null : $i;
}
public function indexOfLast($needle, int $offset = 0): ?int
{
if ($needle instanceof AbstractString) {
$needle = $needle->string;
} elseif (\is_array($needle) || $needle instanceof \Traversable) {
return parent::indexOfLast($needle, $offset);
} else {
$needle = (string) $needle;
}
if ('' === $needle) {
return null;
}
$i = $this->ignoreCase ? mb_strripos($this->string, $needle, $offset, 'UTF-8') : mb_strrpos($this->string, $needle, $offset, 'UTF-8');
return false === $i ? null : $i;
}
public function length(): int
{
return mb_strlen($this->string, 'UTF-8');
}
public function prepend(string ...$prefix): AbstractString
{
$str = clone $this;
$str->string = (1 >= \count($prefix) ? ($prefix[0] ?? '') : implode('', $prefix)).$this->string;
if (!preg_match('//u', $str->string)) {
throw new InvalidArgumentException('Invalid UTF-8 string.');
}
return $str;
}
public function replace(string $from, string $to): AbstractString
{
$str = clone $this;
if ('' === $from || !preg_match('//u', $from)) {
return $str;
}
if ('' !== $to && !preg_match('//u', $to)) {
throw new InvalidArgumentException('Invalid UTF-8 string.');
}
if ($this->ignoreCase) {
$str->string = implode($to, preg_split('{'.preg_quote($from).'}iuD', $this->string));
} else {
$str->string = str_replace($from, $to, $this->string);
}
return $str;
}
public function slice(int $start = 0, int $length = null): AbstractString
{
$str = clone $this;
$str->string = mb_substr($this->string, $start, $length, 'UTF-8');
return $str;
}
public function splice(string $replacement, int $start = 0, int $length = null): AbstractString
{
if (!preg_match('//u', $replacement)) {
throw new InvalidArgumentException('Invalid UTF-8 string.');
}
$str = clone $this;
$start = $start ? \strlen(mb_substr($this->string, 0, $start, 'UTF-8')) : 0;
$length = $length ? \strlen(mb_substr($this->string, $start, $length, 'UTF-8')) : $length;
$str->string = substr_replace($this->string, $replacement, $start, $length ?? \PHP_INT_MAX);
return $str;
}
public function split(string $delimiter, int $limit = null, int $flags = null): array
{
if (1 > $limit = $limit ?? \PHP_INT_MAX) {
throw new InvalidArgumentException('Split limit must be a positive integer.');
}
if ('' === $delimiter) {
throw new InvalidArgumentException('Split delimiter is empty.');
}
if (null !== $flags) {
return parent::split($delimiter.'u', $limit, $flags);
}
if (!preg_match('//u', $delimiter)) {
throw new InvalidArgumentException('Split delimiter is not a valid UTF-8 string.');
}
$str = clone $this;
$chunks = $this->ignoreCase
? preg_split('{'.preg_quote($delimiter).'}iuD', $this->string, $limit)
: explode($delimiter, $this->string, $limit);
foreach ($chunks as &$chunk) {
$str->string = $chunk;
$chunk = clone $str;
}
return $chunks;
}
public function startsWith($prefix): bool
{
if ($prefix instanceof AbstractString) {
$prefix = $prefix->string;
} elseif (\is_array($prefix) || $prefix instanceof \Traversable) {
return parent::startsWith($prefix);
} else {
$prefix = (string) $prefix;
}
if ('' === $prefix || !preg_match('//u', $prefix)) {
return false;
}
if ($this->ignoreCase) {
return 0 === mb_stripos($this->string, $prefix, 0, 'UTF-8');
}
return 0 === strncmp($this->string, $prefix, \strlen($prefix));
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\String;
/**
* A string whose value is computed lazily by a callback.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
class LazyString implements \Stringable, \JsonSerializable
{
private $value;
/**
* @param callable|array $callback A callable or a [Closure, method] lazy-callable
*
* @return static
*/
public static function fromCallable($callback, ...$arguments): self
{
if (!\is_callable($callback) && !(\is_array($callback) && isset($callback[0]) && $callback[0] instanceof \Closure && 2 >= \count($callback))) {
throw new \TypeError(sprintf('Argument 1 passed to "%s()" must be a callable or a [Closure, method] lazy-callable, "%s" given.', __METHOD__, get_debug_type($callback)));
}
$lazyString = new static();
$lazyString->value = static function () use (&$callback, &$arguments, &$value): string {
if (null !== $arguments) {
if (!\is_callable($callback)) {
$callback[0] = $callback[0]();
$callback[1] = $callback[1] ?? '__invoke';
}
$value = $callback(...$arguments);
$callback = self::getPrettyName($callback);
$arguments = null;
}
return $value ?? '';
};
return $lazyString;
}
/**
* @param string|int|float|bool|\Stringable $value
*
* @return static
*/
public static function fromStringable($value): self
{
if (!self::isStringable($value)) {
throw new \TypeError(sprintf('Argument 1 passed to "%s()" must be a scalar or a stringable object, "%s" given.', __METHOD__, get_debug_type($value)));
}
if (\is_object($value)) {
return static::fromCallable([$value, '__toString']);
}
$lazyString = new static();
$lazyString->value = (string) $value;
return $lazyString;
}
/**
* Tells whether the provided value can be cast to string.
*/
final public static function isStringable($value): bool
{
return \is_string($value) || $value instanceof self || (\is_object($value) ? method_exists($value, '__toString') : is_scalar($value));
}
/**
* Casts scalars and stringable objects to strings.
*
* @param object|string|int|float|bool $value
*
* @throws \TypeError When the provided value is not stringable
*/
final public static function resolve($value): string
{
return $value;
}
/**
* @return string
*/
public function __toString()
{
if (\is_string($this->value)) {
return $this->value;
}
try {
return $this->value = ($this->value)();
} catch (\Throwable $e) {
if (\TypeError::class === \get_class($e) && __FILE__ === $e->getFile()) {
$type = explode(', ', $e->getMessage());
$type = substr(array_pop($type), 0, -\strlen(' returned'));
$r = new \ReflectionFunction($this->value);
$callback = $r->getStaticVariables()['callback'];
$e = new \TypeError(sprintf('Return value of %s() passed to %s::fromCallable() must be of the type string, %s returned.', $callback, static::class, $type));
}
if (\PHP_VERSION_ID < 70400) {
// leverage the ErrorHandler component with graceful fallback when it's not available
return trigger_error($e, \E_USER_ERROR);
}
throw $e;
}
}
public function __sleep(): array
{
$this->__toString();
return ['value'];
}
public function jsonSerialize(): string
{
return $this->__toString();
}
private function __construct()
{
}
private static function getPrettyName(callable $callback): string
{
if (\is_string($callback)) {
return $callback;
}
if (\is_array($callback)) {
$class = \is_object($callback[0]) ? get_debug_type($callback[0]) : $callback[0];
$method = $callback[1];
} elseif ($callback instanceof \Closure) {
$r = new \ReflectionFunction($callback);
if (false !== strpos($r->name, '{closure}') || !$class = $r->getClosureScopeClass()) {
return $r->name;
}
$class = $class->name;
$method = $r->name;
} else {
$class = get_debug_type($callback);
$method = '__invoke';
}
return $class.'::'.$method;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Yaml\Command;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\Yaml\Exception\ParseException;
use Symfony\Component\Yaml\Parser;
use Symfony\Component\Yaml\Yaml;
/**
* Validates YAML files syntax and outputs encountered errors.
*
* @author Grégoire Pineau <lyrixx@lyrixx.info>
* @author Robin Chalas <robin.chalas@gmail.com>
*/
class LintCommand extends Command
{
protected static $defaultName = 'lint:yaml';
private $parser;
private $format;
private $displayCorrectFiles;
private $directoryIteratorProvider;
private $isReadableProvider;
public function __construct(string $name = null, callable $directoryIteratorProvider = null, callable $isReadableProvider = null)
{
parent::__construct($name);
$this->directoryIteratorProvider = $directoryIteratorProvider;
$this->isReadableProvider = $isReadableProvider;
}
/**
* {@inheritdoc}
*/
protected function configure()
{
$this
->setDescription('Lints a file and outputs encountered errors')
->addArgument('filename', InputArgument::IS_ARRAY, 'A file, a directory or "-" for reading from STDIN')
->addOption('format', null, InputOption::VALUE_REQUIRED, 'The output format', 'txt')
->addOption('parse-tags', null, InputOption::VALUE_NONE, 'Parse custom tags')
->setHelp(<<<EOF
The <info>%command.name%</info> command lints a YAML file and outputs to STDOUT
the first encountered syntax error.
You can validates YAML contents passed from STDIN:
<info>cat filename | php %command.full_name% -</info>
You can also validate the syntax of a file:
<info>php %command.full_name% filename</info>
Or of a whole directory:
<info>php %command.full_name% dirname</info>
<info>php %command.full_name% dirname --format=json</info>
EOF
)
;
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$io = new SymfonyStyle($input, $output);
$filenames = (array) $input->getArgument('filename');
$this->format = $input->getOption('format');
$this->displayCorrectFiles = $output->isVerbose();
$flags = $input->getOption('parse-tags') ? Yaml::PARSE_CUSTOM_TAGS : 0;
if (['-'] === $filenames) {
return $this->display($io, [$this->validate(file_get_contents('php://stdin'), $flags)]);
}
if (!$filenames) {
throw new RuntimeException('Please provide a filename or pipe file content to STDIN.');
}
$filesInfo = [];
foreach ($filenames as $filename) {
if (!$this->isReadable($filename)) {
throw new RuntimeException(sprintf('File or directory "%s" is not readable.', $filename));
}
foreach ($this->getFiles($filename) as $file) {
$filesInfo[] = $this->validate(file_get_contents($file), $flags, $file);
}
}
return $this->display($io, $filesInfo);
}
private function validate(string $content, int $flags, string $file = null)
{
$prevErrorHandler = set_error_handler(function ($level, $message, $file, $line) use (&$prevErrorHandler) {
if (\E_USER_DEPRECATED === $level) {
throw new ParseException($message, $this->getParser()->getRealCurrentLineNb() + 1);
}
return $prevErrorHandler ? $prevErrorHandler($level, $message, $file, $line) : false;
});
try {
$this->getParser()->parse($content, Yaml::PARSE_CONSTANT | $flags);
} catch (ParseException $e) {
return ['file' => $file, 'line' => $e->getParsedLine(), 'valid' => false, 'message' => $e->getMessage()];
} finally {
restore_error_handler();
}
return ['file' => $file, 'valid' => true];
}
private function display(SymfonyStyle $io, array $files): int
{
switch ($this->format) {
case 'txt':
return $this->displayTxt($io, $files);
case 'json':
return $this->displayJson($io, $files);
default:
throw new InvalidArgumentException(sprintf('The format "%s" is not supported.', $this->format));
}
}
private function displayTxt(SymfonyStyle $io, array $filesInfo): int
{
$countFiles = \count($filesInfo);
$erroredFiles = 0;
$suggestTagOption = false;
foreach ($filesInfo as $info) {
if ($info['valid'] && $this->displayCorrectFiles) {
$io->comment('<info>OK</info>'.($info['file'] ? sprintf(' in %s', $info['file']) : ''));
} elseif (!$info['valid']) {
++$erroredFiles;
$io->text('<error> ERROR </error>'.($info['file'] ? sprintf(' in %s', $info['file']) : ''));
$io->text(sprintf('<error> >> %s</error>', $info['message']));
if (false !== strpos($info['message'], 'PARSE_CUSTOM_TAGS')) {
$suggestTagOption = true;
}
}
}
if (0 === $erroredFiles) {
$io->success(sprintf('All %d YAML files contain valid syntax.', $countFiles));
} else {
$io->warning(sprintf('%d YAML files have valid syntax and %d contain errors.%s', $countFiles - $erroredFiles, $erroredFiles, $suggestTagOption ? ' Use the --parse-tags option if you want parse custom tags.' : ''));
}
return min($erroredFiles, 1);
}
private function displayJson(SymfonyStyle $io, array $filesInfo): int
{
$errors = 0;
array_walk($filesInfo, function (&$v) use (&$errors) {
$v['file'] = (string) $v['file'];
if (!$v['valid']) {
++$errors;
}
if (isset($v['message']) && false !== strpos($v['message'], 'PARSE_CUSTOM_TAGS')) {
$v['message'] .= ' Use the --parse-tags option if you want parse custom tags.';
}
});
$io->writeln(json_encode($filesInfo, \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES));
return min($errors, 1);
}
private function getFiles(string $fileOrDirectory): iterable
{
if (is_file($fileOrDirectory)) {
yield new \SplFileInfo($fileOrDirectory);
return;
}
foreach ($this->getDirectoryIterator($fileOrDirectory) as $file) {
if (!\in_array($file->getExtension(), ['yml', 'yaml'])) {
continue;
}
yield $file;
}
}
private function getParser(): Parser
{
if (!$this->parser) {
$this->parser = new Parser();
}
return $this->parser;
}
private function getDirectoryIterator(string $directory): iterable
{
$default = function ($directory) {
return new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator($directory, \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS),
\RecursiveIteratorIterator::LEAVES_ONLY
);
};
if (null !== $this->directoryIteratorProvider) {
return ($this->directoryIteratorProvider)($directory, $default);
}
return $default($directory);
}
private function isReadable(string $fileOrDirectory): bool
{
$default = function ($fileOrDirectory) {
return is_readable($fileOrDirectory);
};
if (null !== $this->isReadableProvider) {
return ($this->isReadableProvider)($fileOrDirectory, $default);
}
return $default($fileOrDirectory);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Yaml;
use Symfony\Component\Yaml\Exception\ParseException;
use Symfony\Component\Yaml\Tag\TaggedValue;
/**
* Parser parses YAML strings to convert them to PHP arrays.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @final
*/
class Parser
{
const TAG_PATTERN = '(?P<tag>![\w!.\/:-]+)';
const BLOCK_SCALAR_HEADER_PATTERN = '(?P<separator>\||>)(?P<modifiers>\+|\-|\d+|\+\d+|\-\d+|\d+\+|\d+\-)?(?P<comments> +#.*)?';
private $filename;
private $offset = 0;
private $numberOfParsedLines = 0;
private $totalNumberOfLines;
private $lines = [];
private $currentLineNb = -1;
private $currentLine = '';
private $refs = [];
private $skippedLineNumbers = [];
private $locallySkippedLineNumbers = [];
private $refsBeingParsed = [];
/**
* Parses a YAML file into a PHP value.
*
* @param string $filename The path to the YAML file to be parsed
* @param int $flags A bit field of PARSE_* constants to customize the YAML parser behavior
*
* @return mixed The YAML converted to a PHP value
*
* @throws ParseException If the file could not be read or the YAML is not valid
*/
public function parseFile(string $filename, int $flags = 0)
{
if (!is_file($filename)) {
throw new ParseException(sprintf('File "%s" does not exist.', $filename));
}
if (!is_readable($filename)) {
throw new ParseException(sprintf('File "%s" cannot be read.', $filename));
}
$this->filename = $filename;
try {
return $this->parse(file_get_contents($filename), $flags);
} finally {
$this->filename = null;
}
}
/**
* Parses a YAML string to a PHP value.
*
* @param string $value A YAML string
* @param int $flags A bit field of PARSE_* constants to customize the YAML parser behavior
*
* @return mixed A PHP value
*
* @throws ParseException If the YAML is not valid
*/
public function parse(string $value, int $flags = 0)
{
if (false === preg_match('//u', $value)) {
throw new ParseException('The YAML value does not appear to be valid UTF-8.', -1, null, $this->filename);
}
$this->refs = [];
$mbEncoding = null;
if (2 /* MB_OVERLOAD_STRING */ & (int) ini_get('mbstring.func_overload')) {
$mbEncoding = mb_internal_encoding();
mb_internal_encoding('UTF-8');
}
try {
$data = $this->doParse($value, $flags);
} finally {
if (null !== $mbEncoding) {
mb_internal_encoding($mbEncoding);
}
$this->lines = [];
$this->currentLine = '';
$this->numberOfParsedLines = 0;
$this->refs = [];
$this->skippedLineNumbers = [];
$this->locallySkippedLineNumbers = [];
$this->totalNumberOfLines = null;
}
return $data;
}
private function doParse(string $value, int $flags)
{
$this->currentLineNb = -1;
$this->currentLine = '';
$value = $this->cleanup($value);
$this->lines = explode("\n", $value);
$this->numberOfParsedLines = \count($this->lines);
$this->locallySkippedLineNumbers = [];
if (null === $this->totalNumberOfLines) {
$this->totalNumberOfLines = $this->numberOfParsedLines;
}
if (!$this->moveToNextLine()) {
return null;
}
$data = [];
$context = null;
$allowOverwrite = false;
while ($this->isCurrentLineEmpty()) {
if (!$this->moveToNextLine()) {
return null;
}
}
// Resolves the tag and returns if end of the document
if (null !== ($tag = $this->getLineTag($this->currentLine, $flags, false)) && !$this->moveToNextLine()) {
return new TaggedValue($tag, '');
}
do {
if ($this->isCurrentLineEmpty()) {
continue;
}
// tab?
if ("\t" === $this->currentLine[0]) {
throw new ParseException('A YAML file cannot contain tabs as indentation.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
}
Inline::initialize($flags, $this->getRealCurrentLineNb(), $this->filename);
$isRef = $mergeNode = false;
if ('-' === $this->currentLine[0] && self::preg_match('#^\-((?P<leadspaces>\s+)(?P<value>.+))?$#u', rtrim($this->currentLine), $values)) {
if ($context && 'mapping' == $context) {
throw new ParseException('You cannot define a sequence item when in a mapping.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
}
$context = 'sequence';
if (isset($values['value']) && '&' === $values['value'][0] && self::preg_match('#^&(?P<ref>[^ ]+) *(?P<value>.*)#u', $values['value'], $matches)) {
$isRef = $matches['ref'];
$this->refsBeingParsed[] = $isRef;
$values['value'] = $matches['value'];
}
if (isset($values['value'][1]) && '?' === $values['value'][0] && ' ' === $values['value'][1]) {
throw new ParseException('Complex mappings are not supported.', $this->getRealCurrentLineNb() + 1, $this->currentLine);
}
// array
if (!isset($values['value']) || '' == trim($values['value'], ' ') || 0 === strpos(ltrim($values['value'], ' '), '#')) {
$data[] = $this->parseBlock($this->getRealCurrentLineNb() + 1, $this->getNextEmbedBlock(null, true) ?? '', $flags);
} elseif (null !== $subTag = $this->getLineTag(ltrim($values['value'], ' '), $flags)) {
$data[] = new TaggedValue(
$subTag,
$this->parseBlock($this->getRealCurrentLineNb() + 1, $this->getNextEmbedBlock(null, true), $flags)
);
} else {
if (
isset($values['leadspaces'])
&& (
'!' === $values['value'][0]
|| self::preg_match('#^(?P<key>'.Inline::REGEX_QUOTED_STRING.'|[^ \'"\{\[].*?) *\:(\s+(?P<value>.+?))?\s*$#u', $this->trimTag($values['value']), $matches)
)
) {
// this is a compact notation element, add to next block and parse
$block = $values['value'];
if ($this->isNextLineIndented()) {
$block .= "\n".$this->getNextEmbedBlock($this->getCurrentLineIndentation() + \strlen($values['leadspaces']) + 1);
}
$data[] = $this->parseBlock($this->getRealCurrentLineNb(), $block, $flags);
} else {
$data[] = $this->parseValue($values['value'], $flags, $context);
}
}
if ($isRef) {
$this->refs[$isRef] = end($data);
array_pop($this->refsBeingParsed);
}
} elseif (
self::preg_match('#^(?P<key>(?:![^\s]++\s++)?(?:'.Inline::REGEX_QUOTED_STRING.'|(?:!?!php/const:)?[^ \'"\[\{!].*?)) *\:(\s++(?P<value>.+))?$#u', rtrim($this->currentLine), $values)
&& (false === strpos($values['key'], ' #') || \in_array($values['key'][0], ['"', "'"]))
) {
if ($context && 'sequence' == $context) {
throw new ParseException('You cannot define a mapping item when in a sequence.', $this->currentLineNb + 1, $this->currentLine, $this->filename);
}
$context = 'mapping';
try {
$key = Inline::parseScalar($values['key']);
} catch (ParseException $e) {
$e->setParsedLine($this->getRealCurrentLineNb() + 1);
$e->setSnippet($this->currentLine);
throw $e;
}
if (!\is_string($key) && !\is_int($key)) {
throw new ParseException(sprintf('%s keys are not supported. Quote your evaluable mapping keys instead.', is_numeric($key) ? 'Numeric' : 'Non-string'), $this->getRealCurrentLineNb() + 1, $this->currentLine);
}
// Convert float keys to strings, to avoid being converted to integers by PHP
if (\is_float($key)) {
$key = (string) $key;
}
if ('<<' === $key && (!isset($values['value']) || '&' !== $values['value'][0] || !self::preg_match('#^&(?P<ref>[^ ]+)#u', $values['value'], $refMatches))) {
$mergeNode = true;
$allowOverwrite = true;
if (isset($values['value'][0]) && '*' === $values['value'][0]) {
$refName = substr(rtrim($values['value']), 1);
if (!\array_key_exists($refName, $this->refs)) {
if (false !== $pos = array_search($refName, $this->refsBeingParsed, true)) {
throw new ParseException(sprintf('Circular reference [%s, %s] detected for reference "%s".', implode(', ', \array_slice($this->refsBeingParsed, $pos)), $refName, $refName), $this->currentLineNb + 1, $this->currentLine, $this->filename);
}
throw new ParseException(sprintf('Reference "%s" does not exist.', $refName), $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
}
$refValue = $this->refs[$refName];
if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && $refValue instanceof \stdClass) {
$refValue = (array) $refValue;
}
if (!\is_array($refValue)) {
throw new ParseException('YAML merge keys used with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
}
$data += $refValue; // array union
} else {
if (isset($values['value']) && '' !== $values['value']) {
$value = $values['value'];
} else {
$value = $this->getNextEmbedBlock();
}
$parsed = $this->parseBlock($this->getRealCurrentLineNb() + 1, $value, $flags);
if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && $parsed instanceof \stdClass) {
$parsed = (array) $parsed;
}
if (!\is_array($parsed)) {
throw new ParseException('YAML merge keys used with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
}
if (isset($parsed[0])) {
// If the value associated with the merge key is a sequence, then this sequence is expected to contain mapping nodes
// and each of these nodes is merged in turn according to its order in the sequence. Keys in mapping nodes earlier
// in the sequence override keys specified in later mapping nodes.
foreach ($parsed as $parsedItem) {
if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && $parsedItem instanceof \stdClass) {
$parsedItem = (array) $parsedItem;
}
if (!\is_array($parsedItem)) {
throw new ParseException('Merge items must be arrays.', $this->getRealCurrentLineNb() + 1, $parsedItem, $this->filename);
}
$data += $parsedItem; // array union
}
} else {
// If the value associated with the key is a single mapping node, each of its key/value pairs is inserted into the
// current mapping, unless the key already exists in it.
$data += $parsed; // array union
}
}
} elseif ('<<' !== $key && isset($values['value']) && '&' === $values['value'][0] && self::preg_match('#^&(?P<ref>[^ ]++) *+(?P<value>.*)#u', $values['value'], $matches)) {
$isRef = $matches['ref'];
$this->refsBeingParsed[] = $isRef;
$values['value'] = $matches['value'];
}
$subTag = null;
if ($mergeNode) {
// Merge keys
} elseif (!isset($values['value']) || '' === $values['value'] || '#' === ($values['value'][0] ?? '') || (null !== $subTag = $this->getLineTag($values['value'], $flags)) || '<<' === $key) {
// hash
// if next line is less indented or equal, then it means that the current value is null
if (!$this->isNextLineIndented() && !$this->isNextLineUnIndentedCollection()) {
// Spec: Keys MUST be unique; first one wins.
// But overwriting is allowed when a merge node is used in current block.
if ($allowOverwrite || !isset($data[$key])) {
if (null !== $subTag) {
$data[$key] = new TaggedValue($subTag, '');
} else {
$data[$key] = null;
}
} else {
throw new ParseException(sprintf('Duplicate key "%s" detected.', $key), $this->getRealCurrentLineNb() + 1, $this->currentLine);
}
} else {
// remember the parsed line number here in case we need it to provide some contexts in error messages below
$realCurrentLineNbKey = $this->getRealCurrentLineNb();
$value = $this->parseBlock($this->getRealCurrentLineNb() + 1, $this->getNextEmbedBlock(), $flags);
if ('<<' === $key) {
$this->refs[$refMatches['ref']] = $value;
if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && $value instanceof \stdClass) {
$value = (array) $value;
}
$data += $value;
} elseif ($allowOverwrite || !isset($data[$key])) {
// Spec: Keys MUST be unique; first one wins.
// But overwriting is allowed when a merge node is used in current block.
if (null !== $subTag) {
$data[$key] = new TaggedValue($subTag, $value);
} else {
$data[$key] = $value;
}
} else {
throw new ParseException(sprintf('Duplicate key "%s" detected.', $key), $realCurrentLineNbKey + 1, $this->currentLine);
}
}
} else {
$value = $this->parseValue(rtrim($values['value']), $flags, $context);
// Spec: Keys MUST be unique; first one wins.
// But overwriting is allowed when a merge node is used in current block.
if ($allowOverwrite || !isset($data[$key])) {
$data[$key] = $value;
} else {
throw new ParseException(sprintf('Duplicate key "%s" detected.', $key), $this->getRealCurrentLineNb() + 1, $this->currentLine);
}
}
if ($isRef) {
$this->refs[$isRef] = $data[$key];
array_pop($this->refsBeingParsed);
}
} elseif ('"' === $this->currentLine[0] || "'" === $this->currentLine[0]) {
if (null !== $context) {
throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
}
try {
return Inline::parse($this->lexInlineQuotedString(), $flags, $this->refs);
} catch (ParseException $e) {
$e->setParsedLine($this->getRealCurrentLineNb() + 1);
$e->setSnippet($this->currentLine);
throw $e;
}
} elseif ('{' === $this->currentLine[0]) {
if (null !== $context) {
throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
}
try {
$parsedMapping = Inline::parse($this->lexInlineMapping(), $flags, $this->refs);
while ($this->moveToNextLine()) {
if (!$this->isCurrentLineEmpty()) {
throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
}
}
return $parsedMapping;
} catch (ParseException $e) {
$e->setParsedLine($this->getRealCurrentLineNb() + 1);
$e->setSnippet($this->currentLine);
throw $e;
}
} elseif ('[' === $this->currentLine[0]) {
if (null !== $context) {
throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
}
try {
$parsedSequence = Inline::parse($this->lexInlineSequence(), $flags, $this->refs);
while ($this->moveToNextLine()) {
if (!$this->isCurrentLineEmpty()) {
throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
}
}
return $parsedSequence;
} catch (ParseException $e) {
$e->setParsedLine($this->getRealCurrentLineNb() + 1);
$e->setSnippet($this->currentLine);
throw $e;
}
} else {
// multiple documents are not supported
if ('---' === $this->currentLine) {
throw new ParseException('Multiple documents are not supported.', $this->currentLineNb + 1, $this->currentLine, $this->filename);
}
if ($deprecatedUsage = (isset($this->currentLine[1]) && '?' === $this->currentLine[0] && ' ' === $this->currentLine[1])) {
throw new ParseException('Complex mappings are not supported.', $this->getRealCurrentLineNb() + 1, $this->currentLine);
}
// 1-liner optionally followed by newline(s)
if (\is_string($value) && $this->lines[0] === trim($value)) {
try {
$value = Inline::parse($this->lines[0], $flags, $this->refs);
} catch (ParseException $e) {
$e->setParsedLine($this->getRealCurrentLineNb() + 1);
$e->setSnippet($this->currentLine);
throw $e;
}
return $value;
}
// try to parse the value as a multi-line string as a last resort
if (0 === $this->currentLineNb) {
$previousLineWasNewline = false;
$previousLineWasTerminatedWithBackslash = false;
$value = '';
foreach ($this->lines as $line) {
$trimmedLine = trim($line);
if ('#' === ($trimmedLine[0] ?? '')) {
continue;
}
// If the indentation is not consistent at offset 0, it is to be considered as a ParseError
if (0 === $this->offset && !$deprecatedUsage && isset($line[0]) && ' ' === $line[0]) {
throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
}
if (false !== strpos($line, ': ')) {
throw new ParseException('Mapping values are not allowed in multi-line blocks.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
}
if ('' === $trimmedLine) {
$value .= "\n";
} elseif (!$previousLineWasNewline && !$previousLineWasTerminatedWithBackslash) {
$value .= ' ';
}
if ('' !== $trimmedLine && '\\' === $line[-1]) {
$value .= ltrim(substr($line, 0, -1));
} elseif ('' !== $trimmedLine) {
$value .= $trimmedLine;
}
if ('' === $trimmedLine) {
$previousLineWasNewline = true;
$previousLineWasTerminatedWithBackslash = false;
} elseif ('\\' === $line[-1]) {
$previousLineWasNewline = false;
$previousLineWasTerminatedWithBackslash = true;
} else {
$previousLineWasNewline = false;
$previousLineWasTerminatedWithBackslash = false;
}
}
try {
return Inline::parse(trim($value));
} catch (ParseException $e) {
// fall-through to the ParseException thrown below
}
}
throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
}
} while ($this->moveToNextLine());
if (null !== $tag) {
$data = new TaggedValue($tag, $data);
}
if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && 'mapping' === $context && !\is_object($data)) {
$object = new \stdClass();
foreach ($data as $key => $value) {
$object->$key = $value;
}
$data = $object;
}
return empty($data) ? null : $data;
}
private function parseBlock(int $offset, string $yaml, int $flags)
{
$skippedLineNumbers = $this->skippedLineNumbers;
foreach ($this->locallySkippedLineNumbers as $lineNumber) {
if ($lineNumber < $offset) {
continue;
}
$skippedLineNumbers[] = $lineNumber;
}
$parser = new self();
$parser->offset = $offset;
$parser->totalNumberOfLines = $this->totalNumberOfLines;
$parser->skippedLineNumbers = $skippedLineNumbers;
$parser->refs = &$this->refs;
$parser->refsBeingParsed = $this->refsBeingParsed;
return $parser->doParse($yaml, $flags);
}
/**
* Returns the current line number (takes the offset into account).
*
* @internal
*
* @return int The current line number
*/
public function getRealCurrentLineNb(): int
{
$realCurrentLineNumber = $this->currentLineNb + $this->offset;
foreach ($this->skippedLineNumbers as $skippedLineNumber) {
if ($skippedLineNumber > $realCurrentLineNumber) {
break;
}
++$realCurrentLineNumber;
}
return $realCurrentLineNumber;
}
/**
* Returns the current line indentation.
*
* @return int The current line indentation
*/
private function getCurrentLineIndentation(): int
{
if (' ' !== ($this->currentLine[0] ?? '')) {
return 0;
}
return \strlen($this->currentLine) - \strlen(ltrim($this->currentLine, ' '));
}
/**
* Returns the next embed block of YAML.
*
* @param int|null $indentation The indent level at which the block is to be read, or null for default
* @param bool $inSequence True if the enclosing data structure is a sequence
*
* @return string A YAML string
*
* @throws ParseException When indentation problem are detected
*/
private function getNextEmbedBlock(int $indentation = null, bool $inSequence = false): string
{
$oldLineIndentation = $this->getCurrentLineIndentation();
if (!$this->moveToNextLine()) {
return '';
}
if (null === $indentation) {
$newIndent = null;
$movements = 0;
do {
$EOF = false;
// empty and comment-like lines do not influence the indentation depth
if ($this->isCurrentLineEmpty() || $this->isCurrentLineComment()) {
$EOF = !$this->moveToNextLine();
if (!$EOF) {
++$movements;
}
} else {
$newIndent = $this->getCurrentLineIndentation();
}
} while (!$EOF && null === $newIndent);
for ($i = 0; $i < $movements; ++$i) {
$this->moveToPreviousLine();
}
$unindentedEmbedBlock = $this->isStringUnIndentedCollectionItem();
if (!$this->isCurrentLineEmpty() && 0 === $newIndent && !$unindentedEmbedBlock) {
throw new ParseException('Indentation problem.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
}
} else {
$newIndent = $indentation;
}
$data = [];
if ($this->getCurrentLineIndentation() >= $newIndent) {
$data[] = substr($this->currentLine, $newIndent);
} elseif ($this->isCurrentLineEmpty() || $this->isCurrentLineComment()) {
$data[] = $this->currentLine;
} else {
$this->moveToPreviousLine();
return '';
}
if ($inSequence && $oldLineIndentation === $newIndent && isset($data[0][0]) && '-' === $data[0][0]) {
// the previous line contained a dash but no item content, this line is a sequence item with the same indentation
// and therefore no nested list or mapping
$this->moveToPreviousLine();
return '';
}
$isItUnindentedCollection = $this->isStringUnIndentedCollectionItem();
$isItComment = $this->isCurrentLineComment();
while ($this->moveToNextLine()) {
if ($isItComment && !$isItUnindentedCollection) {
$isItUnindentedCollection = $this->isStringUnIndentedCollectionItem();
$isItComment = $this->isCurrentLineComment();
}
$indent = $this->getCurrentLineIndentation();
if ($isItUnindentedCollection && !$this->isCurrentLineEmpty() && !$this->isStringUnIndentedCollectionItem() && $newIndent === $indent) {
$this->moveToPreviousLine();
break;
}
if ($this->isCurrentLineBlank()) {
$data[] = substr($this->currentLine, $newIndent);
continue;
}
if ($indent >= $newIndent) {
$data[] = substr($this->currentLine, $newIndent);
} elseif ($this->isCurrentLineComment()) {
$data[] = $this->currentLine;
} elseif (0 == $indent) {
$this->moveToPreviousLine();
break;
} else {
throw new ParseException('Indentation problem.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
}
}
return implode("\n", $data);
}
private function hasMoreLines(): bool
{
return (\count($this->lines) - 1) > $this->currentLineNb;
}
/**
* Moves the parser to the next line.
*/
private function moveToNextLine(): bool
{
if ($this->currentLineNb >= $this->numberOfParsedLines - 1) {
return false;
}
$this->currentLine = $this->lines[++$this->currentLineNb];
return true;
}
/**
* Moves the parser to the previous line.
*/
private function moveToPreviousLine(): bool
{
if ($this->currentLineNb < 1) {
return false;
}
$this->currentLine = $this->lines[--$this->currentLineNb];
return true;
}
/**
* Parses a YAML value.
*
* @param string $value A YAML value
* @param int $flags A bit field of PARSE_* constants to customize the YAML parser behavior
* @param string $context The parser context (either sequence or mapping)
*
* @return mixed A PHP value
*
* @throws ParseException When reference does not exist
*/
private function parseValue(string $value, int $flags, string $context)
{
if ('*' === ($value[0] ?? '')) {
if (false !== $pos = strpos($value, '#')) {
$value = substr($value, 1, $pos - 2);
} else {
$value = substr($value, 1);
}
if (!\array_key_exists($value, $this->refs)) {
if (false !== $pos = array_search($value, $this->refsBeingParsed, true)) {
throw new ParseException(sprintf('Circular reference [%s, %s] detected for reference "%s".', implode(', ', \array_slice($this->refsBeingParsed, $pos)), $value, $value), $this->currentLineNb + 1, $this->currentLine, $this->filename);
}
throw new ParseException(sprintf('Reference "%s" does not exist.', $value), $this->currentLineNb + 1, $this->currentLine, $this->filename);
}
return $this->refs[$value];
}
if (\in_array($value[0], ['!', '|', '>'], true) && self::preg_match('/^(?:'.self::TAG_PATTERN.' +)?'.self::BLOCK_SCALAR_HEADER_PATTERN.'$/', $value, $matches)) {
$modifiers = isset($matches['modifiers']) ? $matches['modifiers'] : '';
$data = $this->parseBlockScalar($matches['separator'], preg_replace('#\d+#', '', $modifiers), abs((int) $modifiers));
if ('' !== $matches['tag'] && '!' !== $matches['tag']) {
if ('!!binary' === $matches['tag']) {
return Inline::evaluateBinaryScalar($data);
}
return new TaggedValue(substr($matches['tag'], 1), $data);
}
return $data;
}
try {
if ('' !== $value && '{' === $value[0]) {
$cursor = \strlen($this->currentLine) - \strlen($value);
return Inline::parse($this->lexInlineMapping($cursor), $flags, $this->refs);
} elseif ('' !== $value && '[' === $value[0]) {
$cursor = \strlen($this->currentLine) - \strlen($value);
return Inline::parse($this->lexInlineSequence($cursor), $flags, $this->refs);
}
switch ($value[0] ?? '') {
case '"':
case "'":
$cursor = \strlen($this->currentLine) - \strlen($value);
$parsedValue = Inline::parse($this->lexInlineQuotedString($cursor), $flags, $this->refs);
if (isset($this->currentLine[$cursor]) && preg_replace('/\s*#.*$/A', '', substr($this->currentLine, $cursor))) {
throw new ParseException(sprintf('Unexpected characters near "%s".', substr($this->currentLine, $cursor)));
}
return $parsedValue;
default:
$lines = [];
while ($this->moveToNextLine()) {
// unquoted strings end before the first unindented line
if (0 === $this->getCurrentLineIndentation()) {
$this->moveToPreviousLine();
break;
}
$lines[] = trim($this->currentLine);
}
for ($i = 0, $linesCount = \count($lines), $previousLineBlank = false; $i < $linesCount; ++$i) {
if ('' === $lines[$i]) {
$value .= "\n";
$previousLineBlank = true;
} elseif ($previousLineBlank) {
$value .= $lines[$i];
$previousLineBlank = false;
} else {
$value .= ' '.$lines[$i];
$previousLineBlank = false;
}
}
Inline::$parsedLineNumber = $this->getRealCurrentLineNb();
$parsedValue = Inline::parse($value, $flags, $this->refs);
if ('mapping' === $context && \is_string($parsedValue) && '"' !== $value[0] && "'" !== $value[0] && '[' !== $value[0] && '{' !== $value[0] && '!' !== $value[0] && false !== strpos($parsedValue, ': ')) {
throw new ParseException('A colon cannot be used in an unquoted mapping value.', $this->getRealCurrentLineNb() + 1, $value, $this->filename);
}
return $parsedValue;
}
} catch (ParseException $e) {
$e->setParsedLine($this->getRealCurrentLineNb() + 1);
$e->setSnippet($this->currentLine);
throw $e;
}
}
/**
* Parses a block scalar.
*
* @param string $style The style indicator that was used to begin this block scalar (| or >)
* @param string $chomping The chomping indicator that was used to begin this block scalar (+ or -)
* @param int $indentation The indentation indicator that was used to begin this block scalar
*/
private function parseBlockScalar(string $style, string $chomping = '', int $indentation = 0): string
{
$notEOF = $this->moveToNextLine();
if (!$notEOF) {
return '';
}
$isCurrentLineBlank = $this->isCurrentLineBlank();
$blockLines = [];
// leading blank lines are consumed before determining indentation
while ($notEOF && $isCurrentLineBlank) {
// newline only if not EOF
if ($notEOF = $this->moveToNextLine()) {
$blockLines[] = '';
$isCurrentLineBlank = $this->isCurrentLineBlank();
}
}
// determine indentation if not specified
if (0 === $indentation) {
$currentLineLength = \strlen($this->currentLine);
for ($i = 0; $i < $currentLineLength && ' ' === $this->currentLine[$i]; ++$i) {
++$indentation;
}
}
if ($indentation > 0) {
$pattern = sprintf('/^ {%d}(.*)$/', $indentation);
while (
$notEOF && (
$isCurrentLineBlank ||
self::preg_match($pattern, $this->currentLine, $matches)
)
) {
if ($isCurrentLineBlank && \strlen($this->currentLine) > $indentation) {
$blockLines[] = substr($this->currentLine, $indentation);
} elseif ($isCurrentLineBlank) {
$blockLines[] = '';
} else {
$blockLines[] = $matches[1];
}
// newline only if not EOF
if ($notEOF = $this->moveToNextLine()) {
$isCurrentLineBlank = $this->isCurrentLineBlank();
}
}
} elseif ($notEOF) {
$blockLines[] = '';
}
if ($notEOF) {
$blockLines[] = '';
$this->moveToPreviousLine();
} elseif (!$notEOF && !$this->isCurrentLineLastLineInDocument()) {
$blockLines[] = '';
}
// folded style
if ('>' === $style) {
$text = '';
$previousLineIndented = false;
$previousLineBlank = false;
for ($i = 0, $blockLinesCount = \count($blockLines); $i < $blockLinesCount; ++$i) {
if ('' === $blockLines[$i]) {
$text .= "\n";
$previousLineIndented = false;
$previousLineBlank = true;
} elseif (' ' === $blockLines[$i][0]) {
$text .= "\n".$blockLines[$i];
$previousLineIndented = true;
$previousLineBlank = false;
} elseif ($previousLineIndented) {
$text .= "\n".$blockLines[$i];
$previousLineIndented = false;
$previousLineBlank = false;
} elseif ($previousLineBlank || 0 === $i) {
$text .= $blockLines[$i];
$previousLineIndented = false;
$previousLineBlank = false;
} else {
$text .= ' '.$blockLines[$i];
$previousLineIndented = false;
$previousLineBlank = false;
}
}
} else {
$text = implode("\n", $blockLines);
}
// deal with trailing newlines
if ('' === $chomping) {
$text = preg_replace('/\n+$/', "\n", $text);
} elseif ('-' === $chomping) {
$text = preg_replace('/\n+$/', '', $text);
}
return $text;
}
/**
* Returns true if the next line is indented.
*
* @return bool Returns true if the next line is indented, false otherwise
*/
private function isNextLineIndented(): bool
{
$currentIndentation = $this->getCurrentLineIndentation();
$movements = 0;
do {
$EOF = !$this->moveToNextLine();
if (!$EOF) {
++$movements;
}
} while (!$EOF && ($this->isCurrentLineEmpty() || $this->isCurrentLineComment()));
if ($EOF) {
return false;
}
$ret = $this->getCurrentLineIndentation() > $currentIndentation;
for ($i = 0; $i < $movements; ++$i) {
$this->moveToPreviousLine();
}
return $ret;
}
/**
* Returns true if the current line is blank or if it is a comment line.
*
* @return bool Returns true if the current line is empty or if it is a comment line, false otherwise
*/
private function isCurrentLineEmpty(): bool
{
return $this->isCurrentLineBlank() || $this->isCurrentLineComment();
}
/**
* Returns true if the current line is blank.
*
* @return bool Returns true if the current line is blank, false otherwise
*/
private function isCurrentLineBlank(): bool
{
return '' === $this->currentLine || '' === trim($this->currentLine, ' ');
}
/**
* Returns true if the current line is a comment line.
*
* @return bool Returns true if the current line is a comment line, false otherwise
*/
private function isCurrentLineComment(): bool
{
//checking explicitly the first char of the trim is faster than loops or strpos
$ltrimmedLine = '' !== $this->currentLine && ' ' === $this->currentLine[0] ? ltrim($this->currentLine, ' ') : $this->currentLine;
return '' !== $ltrimmedLine && '#' === $ltrimmedLine[0];
}
private function isCurrentLineLastLineInDocument(): bool
{
return ($this->offset + $this->currentLineNb) >= ($this->totalNumberOfLines - 1);
}
/**
* Cleanups a YAML string to be parsed.
*
* @param string $value The input YAML string
*
* @return string A cleaned up YAML string
*/
private function cleanup(string $value): string
{
$value = str_replace(["\r\n", "\r"], "\n", $value);
// strip YAML header
$count = 0;
$value = preg_replace('#^\%YAML[: ][\d\.]+.*\n#u', '', $value, -1, $count);
$this->offset += $count;
// remove leading comments
$trimmedValue = preg_replace('#^(\#.*?\n)+#s', '', $value, -1, $count);
if (1 === $count) {
// items have been removed, update the offset
$this->offset += substr_count($value, "\n") - substr_count($trimmedValue, "\n");
$value = $trimmedValue;
}
// remove start of the document marker (---)
$trimmedValue = preg_replace('#^\-\-\-.*?\n#s', '', $value, -1, $count);
if (1 === $count) {
// items have been removed, update the offset
$this->offset += substr_count($value, "\n") - substr_count($trimmedValue, "\n");
$value = $trimmedValue;
// remove end of the document marker (...)
$value = preg_replace('#\.\.\.\s*$#', '', $value);
}
return $value;
}
/**
* Returns true if the next line starts unindented collection.
*
* @return bool Returns true if the next line starts unindented collection, false otherwise
*/
private function isNextLineUnIndentedCollection(): bool
{
$currentIndentation = $this->getCurrentLineIndentation();
$movements = 0;
do {
$EOF = !$this->moveToNextLine();
if (!$EOF) {
++$movements;
}
} while (!$EOF && ($this->isCurrentLineEmpty() || $this->isCurrentLineComment()));
if ($EOF) {
return false;
}
$ret = $this->getCurrentLineIndentation() === $currentIndentation && $this->isStringUnIndentedCollectionItem();
for ($i = 0; $i < $movements; ++$i) {
$this->moveToPreviousLine();
}
return $ret;
}
/**
* Returns true if the string is un-indented collection item.
*
* @return bool Returns true if the string is un-indented collection item, false otherwise
*/
private function isStringUnIndentedCollectionItem(): bool
{
return 0 === strncmp($this->currentLine, '- ', 2) || '-' === rtrim($this->currentLine);
}
/**
* A local wrapper for "preg_match" which will throw a ParseException if there
* is an internal error in the PCRE engine.
*
* This avoids us needing to check for "false" every time PCRE is used
* in the YAML engine
*
* @throws ParseException on a PCRE internal error
*
* @see preg_last_error()
*
* @internal
*/
public static function preg_match(string $pattern, string $subject, array &$matches = null, int $flags = 0, int $offset = 0): int
{
if (false === $ret = preg_match($pattern, $subject, $matches, $flags, $offset)) {
switch (preg_last_error()) {
case \PREG_INTERNAL_ERROR:
$error = 'Internal PCRE error.';
break;
case \PREG_BACKTRACK_LIMIT_ERROR:
$error = 'pcre.backtrack_limit reached.';
break;
case \PREG_RECURSION_LIMIT_ERROR:
$error = 'pcre.recursion_limit reached.';
break;
case \PREG_BAD_UTF8_ERROR:
$error = 'Malformed UTF-8 data.';
break;
case \PREG_BAD_UTF8_OFFSET_ERROR:
$error = 'Offset doesn\'t correspond to the begin of a valid UTF-8 code point.';
break;
default:
$error = 'Error.';
}
throw new ParseException($error);
}
return $ret;
}
/**
* Trim the tag on top of the value.
*
* Prevent values such as "!foo {quz: bar}" to be considered as
* a mapping block.
*/
private function trimTag(string $value): string
{
if ('!' === $value[0]) {
return ltrim(substr($value, 1, strcspn($value, " \r\n", 1)), ' ');
}
return $value;
}
private function getLineTag(string $value, int $flags, bool $nextLineCheck = true): ?string
{
if ('' === $value || '!' !== $value[0] || 1 !== self::preg_match('/^'.self::TAG_PATTERN.' *( +#.*)?$/', $value, $matches)) {
return null;
}
if ($nextLineCheck && !$this->isNextLineIndented()) {
return null;
}
$tag = substr($matches['tag'], 1);
// Built-in tags
if ($tag && '!' === $tag[0]) {
throw new ParseException(sprintf('The built-in tag "!%s" is not implemented.', $tag), $this->getRealCurrentLineNb() + 1, $value, $this->filename);
}
if (Yaml::PARSE_CUSTOM_TAGS & $flags) {
return $tag;
}
throw new ParseException(sprintf('Tags support is not enabled. You must use the flag "Yaml::PARSE_CUSTOM_TAGS" to use "%s".', $matches['tag']), $this->getRealCurrentLineNb() + 1, $value, $this->filename);
}
private function lexInlineQuotedString(int &$cursor = 0): string
{
$quotation = $this->currentLine[$cursor];
$value = $quotation;
++$cursor;
$previousLineWasNewline = true;
$previousLineWasTerminatedWithBackslash = false;
$lineNumber = 0;
do {
if (++$lineNumber > 1) {
$cursor += strspn($this->currentLine, ' ', $cursor);
}
if ($this->isCurrentLineBlank()) {
$value .= "\n";
} elseif (!$previousLineWasNewline && !$previousLineWasTerminatedWithBackslash) {
$value .= ' ';
}
for (; \strlen($this->currentLine) > $cursor; ++$cursor) {
switch ($this->currentLine[$cursor]) {
case '\\':
if (isset($this->currentLine[++$cursor])) {
$value .= '\\'.$this->currentLine[$cursor];
}
break;
case $quotation:
++$cursor;
if ("'" === $quotation && isset($this->currentLine[$cursor]) && "'" === $this->currentLine[$cursor]) {
$value .= "''";
break;
}
return $value.$quotation;
default:
$value .= $this->currentLine[$cursor];
}
}
if ($this->isCurrentLineBlank()) {
$previousLineWasNewline = true;
$previousLineWasTerminatedWithBackslash = false;
} elseif ('\\' === $this->currentLine[-1]) {
$previousLineWasNewline = false;
$previousLineWasTerminatedWithBackslash = true;
} else {
$previousLineWasNewline = false;
$previousLineWasTerminatedWithBackslash = false;
}
if ($this->hasMoreLines()) {
$cursor = 0;
}
} while ($this->moveToNextLine());
throw new ParseException('Malformed inline YAML string');
}
private function lexUnquotedString(int &$cursor): string
{
$offset = $cursor;
$cursor += strcspn($this->currentLine, '[]{},: ', $cursor);
return substr($this->currentLine, $offset, $cursor - $offset);
}
private function lexInlineMapping(int &$cursor = 0): string
{
return $this->lexInlineStructure($cursor, '}');
}
private function lexInlineSequence(int &$cursor = 0): string
{
return $this->lexInlineStructure($cursor, ']');
}
private function lexInlineStructure(int &$cursor, string $closingTag): string
{
$value = $this->currentLine[$cursor];
++$cursor;
do {
$this->consumeWhitespaces($cursor);
while (isset($this->currentLine[$cursor])) {
switch ($this->currentLine[$cursor]) {
case '"':
case "'":
$value .= $this->lexInlineQuotedString($cursor);
break;
case ':':
case ',':
$value .= $this->currentLine[$cursor];
++$cursor;
break;
case '{':
$value .= $this->lexInlineMapping($cursor);
break;
case '[':
$value .= $this->lexInlineSequence($cursor);
break;
case $closingTag:
$value .= $this->currentLine[$cursor];
++$cursor;
return $value;
case '#':
break 2;
default:
$value .= $this->lexUnquotedString($cursor);
}
if ($this->consumeWhitespaces($cursor)) {
$value .= ' ';
}
}
if ($this->hasMoreLines()) {
$cursor = 0;
}
} while ($this->moveToNextLine());
throw new ParseException('Malformed inline YAML string');
}
private function consumeWhitespaces(int &$cursor): bool
{
$whitespacesConsumed = 0;
do {
$whitespaceOnlyTokenLength = strspn($this->currentLine, ' ', $cursor);
$whitespacesConsumed += $whitespaceOnlyTokenLength;
$cursor += $whitespaceOnlyTokenLength;
if (isset($this->currentLine[$cursor])) {
return 0 < $whitespacesConsumed;
}
if ($this->hasMoreLines()) {
$cursor = 0;
}
} while ($this->moveToNextLine());
return 0 < $whitespacesConsumed;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Yaml;
use Symfony\Component\Yaml\Exception\ParseException;
/**
* Yaml offers convenience methods to load and dump YAML.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @final
*/
class Yaml
{
const DUMP_OBJECT = 1;
const PARSE_EXCEPTION_ON_INVALID_TYPE = 2;
const PARSE_OBJECT = 4;
const PARSE_OBJECT_FOR_MAP = 8;
const DUMP_EXCEPTION_ON_INVALID_TYPE = 16;
const PARSE_DATETIME = 32;
const DUMP_OBJECT_AS_MAP = 64;
const DUMP_MULTI_LINE_LITERAL_BLOCK = 128;
const PARSE_CONSTANT = 256;
const PARSE_CUSTOM_TAGS = 512;
const DUMP_EMPTY_ARRAY_AS_SEQUENCE = 1024;
const DUMP_NULL_AS_TILDE = 2048;
/**
* Parses a YAML file into a PHP value.
*
* Usage:
*
* $array = Yaml::parseFile('config.yml');
* print_r($array);
*
* @param string $filename The path to the YAML file to be parsed
* @param int $flags A bit field of PARSE_* constants to customize the YAML parser behavior
*
* @return mixed The YAML converted to a PHP value
*
* @throws ParseException If the file could not be read or the YAML is not valid
*/
public static function parseFile(string $filename, int $flags = 0)
{
$yaml = new Parser();
return $yaml->parseFile($filename, $flags);
}
/**
* Parses YAML into a PHP value.
*
* Usage:
* <code>
* $array = Yaml::parse(file_get_contents('config.yml'));
* print_r($array);
* </code>
*
* @param string $input A string containing YAML
* @param int $flags A bit field of PARSE_* constants to customize the YAML parser behavior
*
* @return mixed The YAML converted to a PHP value
*
* @throws ParseException If the YAML is not valid
*/
public static function parse(string $input, int $flags = 0)
{
$yaml = new Parser();
return $yaml->parse($input, $flags);
}
/**
* Dumps a PHP value to a YAML string.
*
* The dump method, when supplied with an array, will do its best
* to convert the array into friendly YAML.
*
* @param mixed $input The PHP value
* @param int $inline The level where you switch to inline YAML
* @param int $indent The amount of spaces to use for indentation of nested nodes
* @param int $flags A bit field of DUMP_* constants to customize the dumped YAML string
*
* @return string A YAML string representing the original PHP value
*/
public static function dump($input, int $inline = 2, int $indent = 4, int $flags = 0): string
{
$yaml = new Dumper($indent);
return $yaml->dump($input, $inline, 0, $flags);
}
}
Copyright (c) 2004-2020 Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Yaml\Exception;
/**
* Exception interface for all exceptions thrown by the component.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
interface ExceptionInterface extends \Throwable
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Yaml\Exception;
/**
* Exception class thrown when an error occurs during parsing.
*
* @author Romain Neutron <imprec@gmail.com>
*/
class RuntimeException extends \RuntimeException implements ExceptionInterface
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Yaml\Exception;
/**
* Exception class thrown when an error occurs during parsing.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class ParseException extends RuntimeException
{
private $parsedFile;
private $parsedLine;
private $snippet;
private $rawMessage;
/**
* @param string $message The error message
* @param int $parsedLine The line where the error occurred
* @param string|null $snippet The snippet of code near the problem
* @param string|null $parsedFile The file name where the error occurred
* @param \Exception|null $previous The previous exception
*/
public function __construct(string $message, int $parsedLine = -1, string $snippet = null, string $parsedFile = null, \Throwable $previous = null)
{
$this->parsedFile = $parsedFile;
$this->parsedLine = $parsedLine;
$this->snippet = $snippet;
$this->rawMessage = $message;
$this->updateRepr();
parent::__construct($this->message, 0, $previous);
}
/**
* Gets the snippet of code near the error.
*
* @return string The snippet of code
*/
public function getSnippet()
{
return $this->snippet;
}
/**
* Sets the snippet of code near the error.
*/
public function setSnippet(string $snippet)
{
$this->snippet = $snippet;
$this->updateRepr();
}
/**
* Gets the filename where the error occurred.
*
* This method returns null if a string is parsed.
*
* @return string The filename
*/
public function getParsedFile()
{
return $this->parsedFile;
}
/**
* Sets the filename where the error occurred.
*/
public function setParsedFile(string $parsedFile)
{
$this->parsedFile = $parsedFile;
$this->updateRepr();
}
/**
* Gets the line where the error occurred.
*
* @return int The file line
*/
public function getParsedLine()
{
return $this->parsedLine;
}
/**
* Sets the line where the error occurred.
*/
public function setParsedLine(int $parsedLine)
{
$this->parsedLine = $parsedLine;
$this->updateRepr();
}
private function updateRepr()
{
$this->message = $this->rawMessage;
$dot = false;
if ('.' === substr($this->message, -1)) {
$this->message = substr($this->message, 0, -1);
$dot = true;
}
if (null !== $this->parsedFile) {
$this->message .= sprintf(' in %s', json_encode($this->parsedFile, \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE));
}
if ($this->parsedLine >= 0) {
$this->message .= sprintf(' at line %d', $this->parsedLine);
}
if ($this->snippet) {
$this->message .= sprintf(' (near "%s")', $this->snippet);
}
if ($dot) {
$this->message .= '.';
}
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Yaml\Exception;
/**
* Exception class thrown when an error occurs during dumping.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class DumpException extends RuntimeException
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Yaml;
use Symfony\Component\Yaml\Tag\TaggedValue;
/**
* Dumper dumps PHP variables to YAML strings.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @final
*/
class Dumper
{
/**
* The amount of spaces to use for indentation of nested nodes.
*
* @var int
*/
protected $indentation;
public function __construct(int $indentation = 4)
{
if ($indentation < 1) {
throw new \InvalidArgumentException('The indentation must be greater than zero.');
}
$this->indentation = $indentation;
}
/**
* Dumps a PHP value to YAML.
*
* @param mixed $input The PHP value
* @param int $inline The level where you switch to inline YAML
* @param int $indent The level of indentation (used internally)
* @param int $flags A bit field of Yaml::DUMP_* constants to customize the dumped YAML string
*
* @return string The YAML representation of the PHP value
*/
public function dump($input, int $inline = 0, int $indent = 0, int $flags = 0): string
{
$output = '';
$prefix = $indent ? str_repeat(' ', $indent) : '';
$dumpObjectAsInlineMap = true;
if (Yaml::DUMP_OBJECT_AS_MAP & $flags && ($input instanceof \ArrayObject || $input instanceof \stdClass)) {
$dumpObjectAsInlineMap = empty((array) $input);
}
if ($inline <= 0 || (!\is_array($input) && !$input instanceof TaggedValue && $dumpObjectAsInlineMap) || empty($input)) {
$output .= $prefix.Inline::dump($input, $flags);
} else {
$dumpAsMap = Inline::isHash($input);
foreach ($input as $key => $value) {
if ($inline >= 1 && Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK & $flags && \is_string($value) && false !== strpos($value, "\n") && false === strpos($value, "\r")) {
// If the first line starts with a space character, the spec requires a blockIndicationIndicator
// http://www.yaml.org/spec/1.2/spec.html#id2793979
$blockIndentationIndicator = (' ' === substr($value, 0, 1)) ? (string) $this->indentation : '';
$output .= sprintf("%s%s%s |%s\n", $prefix, $dumpAsMap ? Inline::dump($key, $flags).':' : '-', '', $blockIndentationIndicator);
foreach (explode("\n", $value) as $row) {
$output .= sprintf("%s%s%s\n", $prefix, str_repeat(' ', $this->indentation), $row);
}
continue;
}
if ($value instanceof TaggedValue) {
$output .= sprintf('%s%s !%s', $prefix, $dumpAsMap ? Inline::dump($key, $flags).':' : '-', $value->getTag());
if ($inline >= 1 && Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK & $flags && \is_string($value->getValue()) && false !== strpos($value->getValue(), "\n") && false === strpos($value->getValue(), "\r\n")) {
// If the first line starts with a space character, the spec requires a blockIndicationIndicator
// http://www.yaml.org/spec/1.2/spec.html#id2793979
$blockIndentationIndicator = (' ' === substr($value->getValue(), 0, 1)) ? (string) $this->indentation : '';
$output .= sprintf(" |%s\n", $blockIndentationIndicator);
foreach (explode("\n", $value->getValue()) as $row) {
$output .= sprintf("%s%s%s\n", $prefix, str_repeat(' ', $this->indentation), $row);
}
continue;
}
if ($inline - 1 <= 0 || null === $value->getValue() || is_scalar($value->getValue())) {
$output .= ' '.$this->dump($value->getValue(), $inline - 1, 0, $flags)."\n";
} else {
$output .= "\n";
$output .= $this->dump($value->getValue(), $inline - 1, $dumpAsMap ? $indent + $this->indentation : $indent + 2, $flags);
}
continue;
}
$dumpObjectAsInlineMap = true;
if (Yaml::DUMP_OBJECT_AS_MAP & $flags && ($value instanceof \ArrayObject || $value instanceof \stdClass)) {
$dumpObjectAsInlineMap = empty((array) $value);
}
$willBeInlined = $inline - 1 <= 0 || !\is_array($value) && $dumpObjectAsInlineMap || empty($value);
$output .= sprintf('%s%s%s%s',
$prefix,
$dumpAsMap ? Inline::dump($key, $flags).':' : '-',
$willBeInlined ? ' ' : "\n",
$this->dump($value, $inline - 1, $willBeInlined ? 0 : $indent + $this->indentation, $flags)
).($willBeInlined ? "\n" : '');
}
}
return $output;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Yaml;
use Symfony\Component\Yaml\Exception\DumpException;
use Symfony\Component\Yaml\Exception\ParseException;
use Symfony\Component\Yaml\Tag\TaggedValue;
/**
* Inline implements a YAML parser/dumper for the YAML inline syntax.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @internal
*/
class Inline
{
const REGEX_QUOTED_STRING = '(?:"([^"\\\\]*+(?:\\\\.[^"\\\\]*+)*+)"|\'([^\']*+(?:\'\'[^\']*+)*+)\')';
public static $parsedLineNumber = -1;
public static $parsedFilename;
private static $exceptionOnInvalidType = false;
private static $objectSupport = false;
private static $objectForMap = false;
private static $constantSupport = false;
public static function initialize(int $flags, int $parsedLineNumber = null, string $parsedFilename = null)
{
self::$exceptionOnInvalidType = (bool) (Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE & $flags);
self::$objectSupport = (bool) (Yaml::PARSE_OBJECT & $flags);
self::$objectForMap = (bool) (Yaml::PARSE_OBJECT_FOR_MAP & $flags);
self::$constantSupport = (bool) (Yaml::PARSE_CONSTANT & $flags);
self::$parsedFilename = $parsedFilename;
if (null !== $parsedLineNumber) {
self::$parsedLineNumber = $parsedLineNumber;
}
}
/**
* Converts a YAML string to a PHP value.
*
* @param string $value A YAML string
* @param int $flags A bit field of PARSE_* constants to customize the YAML parser behavior
* @param array $references Mapping of variable names to values
*
* @return mixed A PHP value
*
* @throws ParseException
*/
public static function parse(string $value = null, int $flags = 0, array $references = [])
{
self::initialize($flags);
$value = trim($value);
if ('' === $value) {
return '';
}
if (2 /* MB_OVERLOAD_STRING */ & (int) ini_get('mbstring.func_overload')) {
$mbEncoding = mb_internal_encoding();
mb_internal_encoding('ASCII');
}
try {
$i = 0;
$tag = self::parseTag($value, $i, $flags);
switch ($value[$i]) {
case '[':
$result = self::parseSequence($value, $flags, $i, $references);
++$i;
break;
case '{':
$result = self::parseMapping($value, $flags, $i, $references);
++$i;
break;
default:
$result = self::parseScalar($value, $flags, null, $i, null === $tag, $references);
}
// some comments are allowed at the end
if (preg_replace('/\s*#.*$/A', '', substr($value, $i))) {
throw new ParseException(sprintf('Unexpected characters near "%s".', substr($value, $i)), self::$parsedLineNumber + 1, $value, self::$parsedFilename);
}
if (null !== $tag && '' !== $tag) {
return new TaggedValue($tag, $result);
}
return $result;
} finally {
if (isset($mbEncoding)) {
mb_internal_encoding($mbEncoding);
}
}
}
/**
* Dumps a given PHP variable to a YAML string.
*
* @param mixed $value The PHP variable to convert
* @param int $flags A bit field of Yaml::DUMP_* constants to customize the dumped YAML string
*
* @return string The YAML string representing the PHP value
*
* @throws DumpException When trying to dump PHP resource
*/
public static function dump($value, int $flags = 0): string
{
switch (true) {
case \is_resource($value):
if (Yaml::DUMP_EXCEPTION_ON_INVALID_TYPE & $flags) {
throw new DumpException(sprintf('Unable to dump PHP resources in a YAML file ("%s").', get_resource_type($value)));
}
return self::dumpNull($flags);
case $value instanceof \DateTimeInterface:
return $value->format('c');
case \is_object($value):
if ($value instanceof TaggedValue) {
return '!'.$value->getTag().' '.self::dump($value->getValue(), $flags);
}
if (Yaml::DUMP_OBJECT & $flags) {
return '!php/object '.self::dump(serialize($value));
}
if (Yaml::DUMP_OBJECT_AS_MAP & $flags && ($value instanceof \stdClass || $value instanceof \ArrayObject)) {
$output = [];
foreach ($value as $key => $val) {
$output[] = sprintf('%s: %s', self::dump($key, $flags), self::dump($val, $flags));
}
return sprintf('{ %s }', implode(', ', $output));
}
if (Yaml::DUMP_EXCEPTION_ON_INVALID_TYPE & $flags) {
throw new DumpException('Object support when dumping a YAML file has been disabled.');
}
return self::dumpNull($flags);
case \is_array($value):
return self::dumpArray($value, $flags);
case null === $value:
return self::dumpNull($flags);
case true === $value:
return 'true';
case false === $value:
return 'false';
case ctype_digit($value):
return \is_string($value) ? "'$value'" : (int) $value;
case is_numeric($value) && false === strpos($value, "\f") && false === strpos($value, "\n") && false === strpos($value, "\r") && false === strpos($value, "\t") && false === strpos($value, "\v"):
$locale = setlocale(\LC_NUMERIC, 0);
if (false !== $locale) {
setlocale(\LC_NUMERIC, 'C');
}
if (\is_float($value)) {
$repr = (string) $value;
if (is_infinite($value)) {
$repr = str_ireplace('INF', '.Inf', $repr);
} elseif (floor($value) == $value && $repr == $value) {
// Preserve float data type since storing a whole number will result in integer value.
$repr = '!!float '.$repr;
}
} else {
$repr = \is_string($value) ? "'$value'" : (string) $value;
}
if (false !== $locale) {
setlocale(\LC_NUMERIC, $locale);
}
return $repr;
case '' == $value:
return "''";
case self::isBinaryString($value):
return '!!binary '.base64_encode($value);
case Escaper::requiresDoubleQuoting($value):
return Escaper::escapeWithDoubleQuotes($value);
case Escaper::requiresSingleQuoting($value):
case Parser::preg_match('{^[0-9]+[_0-9]*$}', $value):
case Parser::preg_match(self::getHexRegex(), $value):
case Parser::preg_match(self::getTimestampRegex(), $value):
return Escaper::escapeWithSingleQuotes($value);
default:
return $value;
}
}
/**
* Check if given array is hash or just normal indexed array.
*
* @param array|\ArrayObject|\stdClass $value The PHP array or array-like object to check
*
* @return bool true if value is hash array, false otherwise
*/
public static function isHash($value): bool
{
if ($value instanceof \stdClass || $value instanceof \ArrayObject) {
return true;
}
$expectedKey = 0;
foreach ($value as $key => $val) {
if ($key !== $expectedKey++) {
return true;
}
}
return false;
}
/**
* Dumps a PHP array to a YAML string.
*
* @param array $value The PHP array to dump
* @param int $flags A bit field of Yaml::DUMP_* constants to customize the dumped YAML string
*
* @return string The YAML string representing the PHP array
*/
private static function dumpArray(array $value, int $flags): string
{
// array
if (($value || Yaml::DUMP_EMPTY_ARRAY_AS_SEQUENCE & $flags) && !self::isHash($value)) {
$output = [];
foreach ($value as $val) {
$output[] = self::dump($val, $flags);
}
return sprintf('[%s]', implode(', ', $output));
}
// hash
$output = [];
foreach ($value as $key => $val) {
$output[] = sprintf('%s: %s', self::dump($key, $flags), self::dump($val, $flags));
}
return sprintf('{ %s }', implode(', ', $output));
}
private static function dumpNull(int $flags): string
{
if (Yaml::DUMP_NULL_AS_TILDE & $flags) {
return '~';
}
return 'null';
}
/**
* Parses a YAML scalar.
*
* @return mixed
*
* @throws ParseException When malformed inline YAML string is parsed
*/
public static function parseScalar(string $scalar, int $flags = 0, array $delimiters = null, int &$i = 0, bool $evaluate = true, array $references = [])
{
if (\in_array($scalar[$i], ['"', "'"], true)) {
// quoted scalar
$output = self::parseQuotedScalar($scalar, $i);
if (null !== $delimiters) {
$tmp = ltrim(substr($scalar, $i), " \n");
if ('' === $tmp) {
throw new ParseException(sprintf('Unexpected end of line, expected one of "%s".', implode('', $delimiters)), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename);
}
if (!\in_array($tmp[0], $delimiters)) {
throw new ParseException(sprintf('Unexpected characters (%s).', substr($scalar, $i)), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename);
}
}
} else {
// "normal" string
if (!$delimiters) {
$output = substr($scalar, $i);
$i += \strlen($output);
// remove comments
if (Parser::preg_match('/[ \t]+#/', $output, $match, \PREG_OFFSET_CAPTURE)) {
$output = substr($output, 0, $match[0][1]);
}
} elseif (Parser::preg_match('/^(.*?)('.implode('|', $delimiters).')/', substr($scalar, $i), $match)) {
$output = $match[1];
$i += \strlen($output);
$output = trim($output);
} else {
throw new ParseException(sprintf('Malformed inline YAML string: "%s".', $scalar), self::$parsedLineNumber + 1, null, self::$parsedFilename);
}
// a non-quoted string cannot start with @ or ` (reserved) nor with a scalar indicator (| or >)
if ($output && ('@' === $output[0] || '`' === $output[0] || '|' === $output[0] || '>' === $output[0] || '%' === $output[0])) {
throw new ParseException(sprintf('The reserved indicator "%s" cannot start a plain scalar; you need to quote the scalar.', $output[0]), self::$parsedLineNumber + 1, $output, self::$parsedFilename);
}
if ($evaluate) {
$output = self::evaluateScalar($output, $flags, $references);
}
}
return $output;
}
/**
* Parses a YAML quoted scalar.
*
* @throws ParseException When malformed inline YAML string is parsed
*/
private static function parseQuotedScalar(string $scalar, int &$i): string
{
if (!Parser::preg_match('/'.self::REGEX_QUOTED_STRING.'/Au', substr($scalar, $i), $match)) {
throw new ParseException(sprintf('Malformed inline YAML string: "%s".', substr($scalar, $i)), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename);
}
$output = substr($match[0], 1, -1);
$unescaper = new Unescaper();
if ('"' == $scalar[$i]) {
$output = $unescaper->unescapeDoubleQuotedString($output);
} else {
$output = $unescaper->unescapeSingleQuotedString($output);
}
$i += \strlen($match[0]);
return $output;
}
/**
* Parses a YAML sequence.
*
* @throws ParseException When malformed inline YAML string is parsed
*/
private static function parseSequence(string $sequence, int $flags, int &$i = 0, array $references = []): array
{
$output = [];
$len = \strlen($sequence);
++$i;
// [foo, bar, ...]
while ($i < $len) {
if (']' === $sequence[$i]) {
return $output;
}
if (',' === $sequence[$i] || ' ' === $sequence[$i]) {
++$i;
continue;
}
$tag = self::parseTag($sequence, $i, $flags);
switch ($sequence[$i]) {
case '[':
// nested sequence
$value = self::parseSequence($sequence, $flags, $i, $references);
break;
case '{':
// nested mapping
$value = self::parseMapping($sequence, $flags, $i, $references);
break;
default:
$isQuoted = \in_array($sequence[$i], ['"', "'"], true);
$value = self::parseScalar($sequence, $flags, [',', ']'], $i, null === $tag, $references);
// the value can be an array if a reference has been resolved to an array var
if (\is_string($value) && !$isQuoted && false !== strpos($value, ': ')) {
// embedded mapping?
try {
$pos = 0;
$value = self::parseMapping('{'.$value.'}', $flags, $pos, $references);
} catch (\InvalidArgumentException $e) {
// no, it's not
}
}
--$i;
}
if (null !== $tag && '' !== $tag) {
$value = new TaggedValue($tag, $value);
}
$output[] = $value;
++$i;
}
throw new ParseException(sprintf('Malformed inline YAML string: "%s".', $sequence), self::$parsedLineNumber + 1, null, self::$parsedFilename);
}
/**
* Parses a YAML mapping.
*
* @return array|\stdClass
*
* @throws ParseException When malformed inline YAML string is parsed
*/
private static function parseMapping(string $mapping, int $flags, int &$i = 0, array $references = [])
{
$output = [];
$len = \strlen($mapping);
++$i;
$allowOverwrite = false;
// {foo: bar, bar:foo, ...}
while ($i < $len) {
switch ($mapping[$i]) {
case ' ':
case ',':
case "\n":
++$i;
continue 2;
case '}':
if (self::$objectForMap) {
return (object) $output;
}
return $output;
}
// key
$offsetBeforeKeyParsing = $i;
$isKeyQuoted = \in_array($mapping[$i], ['"', "'"], true);
$key = self::parseScalar($mapping, $flags, [':', ' '], $i, false, []);
if ($offsetBeforeKeyParsing === $i) {
throw new ParseException('Missing mapping key.', self::$parsedLineNumber + 1, $mapping);
}
if ('!php/const' === $key) {
$key .= ' '.self::parseScalar($mapping, $flags, [':'], $i, false, []);
$key = self::evaluateScalar($key, $flags);
}
if (false === $i = strpos($mapping, ':', $i)) {
break;
}
if (!$isKeyQuoted) {
$evaluatedKey = self::evaluateScalar($key, $flags, $references);
if ('' !== $key && $evaluatedKey !== $key && !\is_string($evaluatedKey) && !\is_int($evaluatedKey)) {
throw new ParseException('Implicit casting of incompatible mapping keys to strings is not supported. Quote your evaluable mapping keys instead.', self::$parsedLineNumber + 1, $mapping);
}
}
if (!$isKeyQuoted && (!isset($mapping[$i + 1]) || !\in_array($mapping[$i + 1], [' ', ',', '[', ']', '{', '}', "\n"], true))) {
throw new ParseException('Colons must be followed by a space or an indication character (i.e. " ", ",", "[", "]", "{", "}").', self::$parsedLineNumber + 1, $mapping);
}
if ('<<' === $key) {
$allowOverwrite = true;
}
while ($i < $len) {
if (':' === $mapping[$i] || ' ' === $mapping[$i] || "\n" === $mapping[$i]) {
++$i;
continue;
}
$tag = self::parseTag($mapping, $i, $flags);
switch ($mapping[$i]) {
case '[':
// nested sequence
$value = self::parseSequence($mapping, $flags, $i, $references);
// Spec: Keys MUST be unique; first one wins.
// Parser cannot abort this mapping earlier, since lines
// are processed sequentially.
// But overwriting is allowed when a merge node is used in current block.
if ('<<' === $key) {
foreach ($value as $parsedValue) {
$output += $parsedValue;
}
} elseif ($allowOverwrite || !isset($output[$key])) {
if (null !== $tag) {
$output[$key] = new TaggedValue($tag, $value);
} else {
$output[$key] = $value;
}
} elseif (isset($output[$key])) {
throw new ParseException(sprintf('Duplicate key "%s" detected.', $key), self::$parsedLineNumber + 1, $mapping);
}
break;
case '{':
// nested mapping
$value = self::parseMapping($mapping, $flags, $i, $references);
// Spec: Keys MUST be unique; first one wins.
// Parser cannot abort this mapping earlier, since lines
// are processed sequentially.
// But overwriting is allowed when a merge node is used in current block.
if ('<<' === $key) {
$output += $value;
} elseif ($allowOverwrite || !isset($output[$key])) {
if (null !== $tag) {
$output[$key] = new TaggedValue($tag, $value);
} else {
$output[$key] = $value;
}
} elseif (isset($output[$key])) {
throw new ParseException(sprintf('Duplicate key "%s" detected.', $key), self::$parsedLineNumber + 1, $mapping);
}
break;
default:
$value = self::parseScalar($mapping, $flags, [',', '}', "\n"], $i, null === $tag, $references);
// Spec: Keys MUST be unique; first one wins.
// Parser cannot abort this mapping earlier, since lines
// are processed sequentially.
// But overwriting is allowed when a merge node is used in current block.
if ('<<' === $key) {
$output += $value;
} elseif ($allowOverwrite || !isset($output[$key])) {
if (null !== $tag) {
$output[$key] = new TaggedValue($tag, $value);
} else {
$output[$key] = $value;
}
} elseif (isset($output[$key])) {
throw new ParseException(sprintf('Duplicate key "%s" detected.', $key), self::$parsedLineNumber + 1, $mapping);
}
--$i;
}
++$i;
continue 2;
}
}
throw new ParseException(sprintf('Malformed inline YAML string: "%s".', $mapping), self::$parsedLineNumber + 1, null, self::$parsedFilename);
}
/**
* Evaluates scalars and replaces magic values.
*
* @return mixed The evaluated YAML string
*
* @throws ParseException when object parsing support was disabled and the parser detected a PHP object or when a reference could not be resolved
*/
private static function evaluateScalar(string $scalar, int $flags, array $references = [])
{
$scalar = trim($scalar);
if ('*' === ($scalar[0] ?? '')) {
if (false !== $pos = strpos($scalar, '#')) {
$value = substr($scalar, 1, $pos - 2);
} else {
$value = substr($scalar, 1);
}
// an unquoted *
if (false === $value || '' === $value) {
throw new ParseException('A reference must contain at least one character.', self::$parsedLineNumber + 1, $value, self::$parsedFilename);
}
if (!\array_key_exists($value, $references)) {
throw new ParseException(sprintf('Reference "%s" does not exist.', $value), self::$parsedLineNumber + 1, $value, self::$parsedFilename);
}
return $references[$value];
}
$scalarLower = strtolower($scalar);
switch (true) {
case 'null' === $scalarLower:
case '' === $scalar:
case '~' === $scalar:
return null;
case 'true' === $scalarLower:
return true;
case 'false' === $scalarLower:
return false;
case '!' === $scalar[0]:
switch (true) {
case 0 === strncmp($scalar, '!!str ', 6):
return (string) substr($scalar, 6);
case 0 === strncmp($scalar, '! ', 2):
return substr($scalar, 2);
case 0 === strncmp($scalar, '!php/object', 11):
if (self::$objectSupport) {
if (!isset($scalar[12])) {
trigger_deprecation('symfony/yaml', '5.1', 'Using the !php/object tag without a value is deprecated.');
return false;
}
return unserialize(self::parseScalar(substr($scalar, 12)));
}
if (self::$exceptionOnInvalidType) {
throw new ParseException('Object support when parsing a YAML file has been disabled.', self::$parsedLineNumber + 1, $scalar, self::$parsedFilename);
}
return null;
case 0 === strncmp($scalar, '!php/const', 10):
if (self::$constantSupport) {
if (!isset($scalar[11])) {
trigger_deprecation('symfony/yaml', '5.1', 'Using the !php/const tag without a value is deprecated.');
return '';
}
$i = 0;
if (\defined($const = self::parseScalar(substr($scalar, 11), 0, null, $i, false))) {
return \constant($const);
}
throw new ParseException(sprintf('The constant "%s" is not defined.', $const), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename);
}
if (self::$exceptionOnInvalidType) {
throw new ParseException(sprintf('The string "%s" could not be parsed as a constant. Did you forget to pass the "Yaml::PARSE_CONSTANT" flag to the parser?', $scalar), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename);
}
return null;
case 0 === strncmp($scalar, '!!float ', 8):
return (float) substr($scalar, 8);
case 0 === strncmp($scalar, '!!binary ', 9):
return self::evaluateBinaryScalar(substr($scalar, 9));
default:
throw new ParseException(sprintf('The string "%s" could not be parsed as it uses an unsupported built-in tag.', $scalar), self::$parsedLineNumber, $scalar, self::$parsedFilename);
}
// no break
case preg_match('/^(?:\+|-)?0o(?P<value>[0-7_]++)$/', $scalar, $matches):
$value = str_replace('_', '', $matches['value']);
if ('-' === $scalar[0]) {
return -octdec($value);
} else {
return octdec($value);
}
// Optimize for returning strings.
// no break
case \in_array($scalar[0], ['+', '-', '.'], true) || is_numeric($scalar[0]):
if (Parser::preg_match('{^[+-]?[0-9][0-9_]*$}', $scalar)) {
$scalar = str_replace('_', '', (string) $scalar);
}
switch (true) {
case ctype_digit($scalar):
if (preg_match('/^0[0-7]+$/', $scalar)) {
trigger_deprecation('symfony/yaml', '5.1', 'Support for parsing numbers prefixed with 0 as octal numbers. They will be parsed as strings as of 6.0.');
return octdec($scalar);
}
$cast = (int) $scalar;
return ($scalar === (string) $cast) ? $cast : $scalar;
case '-' === $scalar[0] && ctype_digit(substr($scalar, 1)):
if (preg_match('/^-0[0-7]+$/', $scalar)) {
trigger_deprecation('symfony/yaml', '5.1', 'Support for parsing numbers prefixed with 0 as octal numbers. They will be parsed as strings as of 6.0.');
return -octdec(substr($scalar, 1));
}
$cast = (int) $scalar;
return ($scalar === (string) $cast) ? $cast : $scalar;
case is_numeric($scalar):
case Parser::preg_match(self::getHexRegex(), $scalar):
$scalar = str_replace('_', '', $scalar);
return '0x' === $scalar[0].$scalar[1] ? hexdec($scalar) : (float) $scalar;
case '.inf' === $scalarLower:
case '.nan' === $scalarLower:
return -log(0);
case '-.inf' === $scalarLower:
return log(0);
case Parser::preg_match('/^(-|\+)?[0-9][0-9_]*(\.[0-9_]+)?$/', $scalar):
return (float) str_replace('_', '', $scalar);
case Parser::preg_match(self::getTimestampRegex(), $scalar):
if (Yaml::PARSE_DATETIME & $flags) {
// When no timezone is provided in the parsed date, YAML spec says we must assume UTC.
return new \DateTime($scalar, new \DateTimeZone('UTC'));
}
$timeZone = date_default_timezone_get();
date_default_timezone_set('UTC');
$time = strtotime($scalar);
date_default_timezone_set($timeZone);
return $time;
}
}
return (string) $scalar;
}
private static function parseTag(string $value, int &$i, int $flags): ?string
{
if ('!' !== $value[$i]) {
return null;
}
$tagLength = strcspn($value, " \t\n[]{},", $i + 1);
$tag = substr($value, $i + 1, $tagLength);
$nextOffset = $i + $tagLength + 1;
$nextOffset += strspn($value, ' ', $nextOffset);
if ('' === $tag && (!isset($value[$nextOffset]) || \in_array($value[$nextOffset], [']', '}', ','], true))) {
throw new ParseException(sprintf('Using the unquoted scalar value "!" is not supported. You must quote it.', $value), self::$parsedLineNumber + 1, $value, self::$parsedFilename);
}
// Is followed by a scalar and is a built-in tag
if ('' !== $tag && (!isset($value[$nextOffset]) || !\in_array($value[$nextOffset], ['[', '{'], true)) && ('!' === $tag[0] || 'str' === $tag || 'php/const' === $tag || 'php/object' === $tag)) {
// Manage in {@link self::evaluateScalar()}
return null;
}
$i = $nextOffset;
// Built-in tags
if ('' !== $tag && '!' === $tag[0]) {
throw new ParseException(sprintf('The built-in tag "!%s" is not implemented.', $tag), self::$parsedLineNumber + 1, $value, self::$parsedFilename);
}
if ('' !== $tag && !isset($value[$i])) {
throw new ParseException(sprintf('Missing value for tag "%s".', $tag), self::$parsedLineNumber + 1, $value, self::$parsedFilename);
}
if ('' === $tag || Yaml::PARSE_CUSTOM_TAGS & $flags) {
return $tag;
}
throw new ParseException(sprintf('Tags support is not enabled. Enable the "Yaml::PARSE_CUSTOM_TAGS" flag to use "!%s".', $tag), self::$parsedLineNumber + 1, $value, self::$parsedFilename);
}
public static function evaluateBinaryScalar(string $scalar): string
{
$parsedBinaryData = self::parseScalar(preg_replace('/\s/', '', $scalar));
if (0 !== (\strlen($parsedBinaryData) % 4)) {
throw new ParseException(sprintf('The normalized base64 encoded data (data without whitespace characters) length must be a multiple of four (%d bytes given).', \strlen($parsedBinaryData)), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename);
}
if (!Parser::preg_match('#^[A-Z0-9+/]+={0,2}$#i', $parsedBinaryData)) {
throw new ParseException(sprintf('The base64 encoded data (%s) contains invalid characters.', $parsedBinaryData), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename);
}
return base64_decode($parsedBinaryData, true);
}
private static function isBinaryString(string $value): bool
{
return !preg_match('//u', $value) || preg_match('/[^\x00\x07-\x0d\x1B\x20-\xff]/', $value);
}
/**
* Gets a regex that matches a YAML date.
*
* @return string The regular expression
*
* @see http://www.yaml.org/spec/1.2/spec.html#id2761573
*/
private static function getTimestampRegex(): string
{
return <<<EOF
~^
(?P<year>[0-9][0-9][0-9][0-9])
-(?P<month>[0-9][0-9]?)
-(?P<day>[0-9][0-9]?)
(?:(?:[Tt]|[ \t]+)
(?P<hour>[0-9][0-9]?)
:(?P<minute>[0-9][0-9])
:(?P<second>[0-9][0-9])
(?:\.(?P<fraction>[0-9]*))?
(?:[ \t]*(?P<tz>Z|(?P<tz_sign>[-+])(?P<tz_hour>[0-9][0-9]?)
(?::(?P<tz_minute>[0-9][0-9]))?))?)?
$~x
EOF;
}
/**
* Gets a regex that matches a YAML number in hexadecimal notation.
*/
private static function getHexRegex(): string
{
return '~^0x[0-9a-f_]++$~i';
}
}
#!/usr/bin/env php
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Runs the Yaml lint command.
*
* @author Jan Schädlich <jan.schaedlich@sensiolabs.de>
*/
use Symfony\Component\Console\Application;
use Symfony\Component\Yaml\Command\LintCommand;
function includeIfExists(string $file): bool
{
return file_exists($file) && include $file;
}
if (
!includeIfExists(__DIR__ . '/../../../../autoload.php') &&
!includeIfExists(__DIR__ . '/../../vendor/autoload.php') &&
!includeIfExists(__DIR__ . '/../../../../../../vendor/autoload.php')
) {
fwrite(STDERR, 'Install dependencies using Composer.'.PHP_EOL);
exit(1);
}
if (!class_exists(Application::class)) {
fwrite(STDERR, 'You need the "symfony/console" component in order to run the Yaml linter.'.PHP_EOL);
exit(1);
}
(new Application())->add($command = new LintCommand())
->getApplication()
->setDefaultCommand($command->getName(), true)
->run()
;
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Yaml;
use Symfony\Component\Yaml\Exception\ParseException;
/**
* Unescaper encapsulates unescaping rules for single and double-quoted
* YAML strings.
*
* @author Matthew Lewinski <matthew@lewinski.org>
*
* @internal
*/
class Unescaper
{
/**
* Regex fragment that matches an escaped character in a double quoted string.
*/
const REGEX_ESCAPED_CHARACTER = '\\\\(x[0-9a-fA-F]{2}|u[0-9a-fA-F]{4}|U[0-9a-fA-F]{8}|.)';
/**
* Unescapes a single quoted string.
*
* @param string $value A single quoted string
*
* @return string The unescaped string
*/
public function unescapeSingleQuotedString(string $value): string
{
return str_replace('\'\'', '\'', $value);
}
/**
* Unescapes a double quoted string.
*
* @param string $value A double quoted string
*
* @return string The unescaped string
*/
public function unescapeDoubleQuotedString(string $value): string
{
$callback = function ($match) {
return $this->unescapeCharacter($match[0]);
};
// evaluate the string
return preg_replace_callback('/'.self::REGEX_ESCAPED_CHARACTER.'/u', $callback, $value);
}
/**
* Unescapes a character that was found in a double-quoted string.
*
* @param string $value An escaped character
*
* @return string The unescaped character
*/
private function unescapeCharacter(string $value): string
{
switch ($value[1]) {
case '0':
return "\x0";
case 'a':
return "\x7";
case 'b':
return "\x8";
case 't':
return "\t";
case "\t":
return "\t";
case 'n':
return "\n";
case 'v':
return "\xB";
case 'f':
return "\xC";
case 'r':
return "\r";
case 'e':
return "\x1B";
case ' ':
return ' ';
case '"':
return '"';
case '/':
return '/';
case '\\':
return '\\';
case 'N':
// U+0085 NEXT LINE
return "\xC2\x85";
case '_':
// U+00A0 NO-BREAK SPACE
return "\xC2\xA0";
case 'L':
// U+2028 LINE SEPARATOR
return "\xE2\x80\xA8";
case 'P':
// U+2029 PARAGRAPH SEPARATOR
return "\xE2\x80\xA9";
case 'x':
return self::utf8chr(hexdec(substr($value, 2, 2)));
case 'u':
return self::utf8chr(hexdec(substr($value, 2, 4)));
case 'U':
return self::utf8chr(hexdec(substr($value, 2, 8)));
default:
throw new ParseException(sprintf('Found unknown escape character "%s".', $value));
}
}
/**
* Get the UTF-8 character for the given code point.
*/
private static function utf8chr(int $c): string
{
if (0x80 > $c %= 0x200000) {
return \chr($c);
}
if (0x800 > $c) {
return \chr(0xC0 | $c >> 6).\chr(0x80 | $c & 0x3F);
}
if (0x10000 > $c) {
return \chr(0xE0 | $c >> 12).\chr(0x80 | $c >> 6 & 0x3F).\chr(0x80 | $c & 0x3F);
}
return \chr(0xF0 | $c >> 18).\chr(0x80 | $c >> 12 & 0x3F).\chr(0x80 | $c >> 6 & 0x3F).\chr(0x80 | $c & 0x3F);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Yaml;
/**
* Escaper encapsulates escaping rules for single and double-quoted
* YAML strings.
*
* @author Matthew Lewinski <matthew@lewinski.org>
*
* @internal
*/
class Escaper
{
// Characters that would cause a dumped string to require double quoting.
const REGEX_CHARACTER_TO_ESCAPE = "[\\x00-\\x1f]|\x7f|\xc2\x85|\xc2\xa0|\xe2\x80\xa8|\xe2\x80\xa9";
// Mapping arrays for escaping a double quoted string. The backslash is
// first to ensure proper escaping because str_replace operates iteratively
// on the input arrays. This ordering of the characters avoids the use of strtr,
// which performs more slowly.
private static $escapees = ['\\', '\\\\', '\\"', '"',
"\x00", "\x01", "\x02", "\x03", "\x04", "\x05", "\x06", "\x07",
"\x08", "\x09", "\x0a", "\x0b", "\x0c", "\x0d", "\x0e", "\x0f",
"\x10", "\x11", "\x12", "\x13", "\x14", "\x15", "\x16", "\x17",
"\x18", "\x19", "\x1a", "\x1b", "\x1c", "\x1d", "\x1e", "\x1f",
"\x7f",
"\xc2\x85", "\xc2\xa0", "\xe2\x80\xa8", "\xe2\x80\xa9",
];
private static $escaped = ['\\\\', '\\"', '\\\\', '\\"',
'\\0', '\\x01', '\\x02', '\\x03', '\\x04', '\\x05', '\\x06', '\\a',
'\\b', '\\t', '\\n', '\\v', '\\f', '\\r', '\\x0e', '\\x0f',
'\\x10', '\\x11', '\\x12', '\\x13', '\\x14', '\\x15', '\\x16', '\\x17',
'\\x18', '\\x19', '\\x1a', '\\e', '\\x1c', '\\x1d', '\\x1e', '\\x1f',
'\\x7f',
'\\N', '\\_', '\\L', '\\P',
];
/**
* Determines if a PHP value would require double quoting in YAML.
*
* @param string $value A PHP value
*
* @return bool True if the value would require double quotes
*/
public static function requiresDoubleQuoting(string $value): bool
{
return 0 < preg_match('/'.self::REGEX_CHARACTER_TO_ESCAPE.'/u', $value);
}
/**
* Escapes and surrounds a PHP value with double quotes.
*
* @param string $value A PHP value
*
* @return string The quoted, escaped string
*/
public static function escapeWithDoubleQuotes(string $value): string
{
return sprintf('"%s"', str_replace(self::$escapees, self::$escaped, $value));
}
/**
* Determines if a PHP value would require single quoting in YAML.
*
* @param string $value A PHP value
*
* @return bool True if the value would require single quotes
*/
public static function requiresSingleQuoting(string $value): bool
{
// Determines if a PHP value is entirely composed of a value that would
// require single quoting in YAML.
if (\in_array(strtolower($value), ['null', '~', 'true', 'false', 'y', 'n', 'yes', 'no', 'on', 'off'])) {
return true;
}
// Determines if the PHP value contains any single characters that would
// cause it to require single quoting in YAML.
return 0 < preg_match('/[ \s \' " \: \{ \} \[ \] , & \* \# \?] | \A[ \- ? | < > = ! % @ ` ]/x', $value);
}
/**
* Escapes and surrounds a PHP value with single quotes.
*
* @param string $value A PHP value
*
* @return string The quoted, escaped string
*/
public static function escapeWithSingleQuotes(string $value): string
{
return sprintf("'%s'", str_replace('\'', '\'\'', $value));
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Yaml\Tag;
/**
* @author Nicolas Grekas <p@tchwork.com>
* @author Guilhem N. <egetick@gmail.com>
*/
final class TaggedValue
{
private $tag;
private $value;
public function __construct(string $tag, $value)
{
$this->tag = $tag;
$this->value = $value;
}
public function getTag(): string
{
return $this->tag;
}
public function getValue()
{
return $this->value;
}
}
<FD><B6><DA><EF>3<AC> <0B><>_<>b<85><62>xQ<78><51>GBMB