#!/usr/bin/env php * Dariusz Rumiński * * This source file is subject to the MIT license that is bundled * with this source code in the file LICENSE. */ if (getenv('PHP_CS_FIXER_FUTURE_MODE')) { error_reporting(-1); } if (defined('HHVM_VERSION_ID')) { fwrite(STDERR, "HHVM is not supported.\n"); if (getenv('PHP_CS_FIXER_IGNORE_ENV')) { fwrite(STDERR, "Ignoring environment requirements because `PHP_CS_FIXER_IGNORE_ENV` is set. Execution may be unstable.\n"); } else { exit(1); } } elseif (!defined('PHP_VERSION_ID') || \PHP_VERSION_ID < 50600 || \PHP_VERSION_ID >= 70500) { fwrite(STDERR, "PHP needs to be a minimum version of PHP 5.6.0 and maximum version of PHP 7.4.*.\n"); if (getenv('PHP_CS_FIXER_IGNORE_ENV')) { fwrite(STDERR, "Ignoring environment requirements because `PHP_CS_FIXER_IGNORE_ENV` is set. Execution may be unstable.\n"); } else { exit(1); } } foreach (['json', 'tokenizer'] as $extension) { if (!extension_loaded($extension)) { fwrite(STDERR, sprintf("PHP extension ext-%s is missing from your system. Install or enable it.\n", $extension)); if (getenv('PHP_CS_FIXER_IGNORE_ENV')) { fwrite(STDERR, "Ignoring environment requirements because `PHP_CS_FIXER_IGNORE_ENV` is set. Execution may be unstable.\n"); } else { exit(1); } } } unset($extension); set_error_handler(static function ($severity, $message, $file, $line) { if ($severity & error_reporting()) { throw new ErrorException($message, 0, $severity, $file, $line); } }); $require = true; if (class_exists('Phar')) { // Maybe this file is used as phar-stub? Let's try! try { Phar::mapPhar('php-cs-fixer.phar'); require_once 'phar://php-cs-fixer.phar/vendor/autoload.php'; $require = false; } catch (PharException $e) { } } if ($require) { // OK, it's not, let give Composer autoloader a try! $possibleFiles = [__DIR__.'/../../autoload.php', __DIR__.'/../autoload.php', __DIR__.'/vendor/autoload.php']; $file = null; foreach ($possibleFiles as $possibleFile) { if (file_exists($possibleFile)) { $file = $possibleFile; break; } } if (null === $file) { throw new RuntimeException('Unable to locate autoload.php file.'); } require_once $file; unset($possibleFiles, $possibleFile, $file); } unset($require); use Composer\XdebugHandler\XdebugHandler; use PhpCsFixer\Console\Application; // Restart if xdebug is loaded, unless the environment variable PHP_CS_FIXER_ALLOW_XDEBUG is set. $xdebug = new XdebugHandler('PHP_CS_FIXER', '--ansi'); $xdebug->check(); unset($xdebug); $application = new Application(); $application->run(); __HALT_COMPILER(); ?> Lvendor/doctrine/annotations/lib/Doctrine/Common/Annotations/CachedReader.phpj[^j&}Svendor/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationException.php-[^-aRvendor/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationRegistry.php[^Jvendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation.php<[^<zXIvendor/doctrine/annotations/lib/Doctrine/Common/Annotations/PhpParser.php[^VOvendor/doctrine/annotations/lib/Doctrine/Common/Annotations/FileCacheReader.php[^qMvendor/doctrine/annotations/lib/Doctrine/Common/Annotations/IndexedReader.php[^nWSVvendor/doctrine/annotations/lib/Doctrine/Common/Annotations/SimpleAnnotationReader.phpJ[^Jq*Ivendor/doctrine/annotations/lib/Doctrine/Common/Annotations/DocParser.php V[^ V`Uvendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Attributes.php[^~Ĵ[vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/IgnoreAnnotation.php[^Qvendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Target.php0[^0 uSvendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Required.phpm[^mZOvendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Enum.phpt[^t]Tvendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Attribute.php[^fʓ۴Pvendor/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationReader.phph[^hzԷFvendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Reader.php"[^"Р@ִHvendor/doctrine/annotations/lib/Doctrine/Common/Annotations/DocLexer.phpw[^w&nKvendor/doctrine/annotations/lib/Doctrine/Common/Annotations/TokenParser.php|[^|wAvendor/doctrine/lexer/lib/Doctrine/Common/Lexer/AbstractLexer.php9 [^9 Z)vendor/psr/log/Psr/Log/AbstractLogger.php;[^;>3[*vendor/psr/log/Psr/Log/LoggerInterface.php[^x%vendor/psr/log/Psr/Log/NullLogger.php[^Di+vendor/psr/log/Psr/Log/LoggerAwareTrait.php[^TB&vendor/psr/log/Psr/Log/LoggerTrait.phpk[^k}/vendor/psr/log/Psr/Log/LoggerAwareInterface.php|[^|$#vendor/psr/log/Psr/Log/LogLevel.php[^j83vendor/psr/log/Psr/Log/InvalidArgumentException.php`[^` X1Kvendor/symfony/debug/FatalErrorHandler/UndefinedMethodFatalErrorHandler.phpO[^OhѴEvendor/symfony/debug/FatalErrorHandler/FatalErrorHandlerInterface.php[^ĹBVMvendor/symfony/debug/FatalErrorHandler/UndefinedFunctionFatalErrorHandler.php[^Fd-Ivendor/symfony/debug/FatalErrorHandler/ClassNotFoundFatalErrorHandler.phpz[^ztr-)vendor/symfony/debug/ExceptionHandler.phpM[^M>eՉ)vendor/symfony/debug/DebugClassLoader.phpV+[^V+uô%vendor/symfony/debug/ErrorHandler.php H[^ H-(vendor/symfony/debug/BufferingLogger.php`[^`Dm۴vendor/symfony/debug/Debug.php+[^+9b9vendor/symfony/debug/Exception/ClassNotFoundException.php[^i7vendor/symfony/debug/Exception/SilencedErrorContext.php([^(+l7vendor/symfony/debug/Exception/OutOfMemoryException.php~[^~o8vendor/symfony/debug/Exception/ContextErrorException.php[^^@;vendor/symfony/debug/Exception/UndefinedMethodException.php[^nش6vendor/symfony/debug/Exception/FatalErrorException.php.[^.J06vendor/symfony/debug/Exception/FatalThrowableError.phpW[^W?K3vendor/symfony/debug/Exception/FlattenException.phpn[^nw =vendor/symfony/debug/Exception/UndefinedFunctionException.php[^J vendor/symfony/finder/Finder.php "[^ "R7%vendor/symfony/finder/SplFileInfo.php[^vendor/symfony/finder/Glob.php[^U)j9vendor/symfony/finder/Exception/AccessDeniedException.php[^s6vendor/symfony/finder/Exception/ExceptionInterface.php[^L~3vendor/symfony/finder/Comparator/DateComparator.php#[^#δ5vendor/symfony/finder/Comparator/NumberComparator.php[^ڡ3/vendor/symfony/finder/Comparator/Comparator.php[^9vendor/symfony/finder/Iterator/FilenameFilterIterator.phpr[^rtu1vendor/symfony/finder/Iterator/FilterIterator.php[^ ;vendor/symfony/finder/Iterator/DepthRangeFilterIterator.php[^͝q=vendor/symfony/finder/Iterator/RecursiveDirectoryIterator.php= [^= Y:vendor/symfony/finder/Iterator/SizeRangeFilterIterator.php`[^`!ky=vendor/symfony/finder/Iterator/MultiplePcreFilterIterator.php [^ ƺ7vendor/symfony/finder/Iterator/CustomFilterIterator.phpX[^XDVSB:vendor/symfony/finder/Iterator/DateRangeFilterIterator.phps[^s=xAvendor/symfony/finder/Iterator/ExcludeDirectoryFilterIterator.php[^f_3vendor/symfony/finder/Iterator/SortableIterator.phpN[^N%<vendor/symfony/finder/Iterator/FilecontentFilterIterator.phpA[^A&ޤ5vendor/symfony/finder/Iterator/PathFilterIterator.php[^F9vendor/symfony/finder/Iterator/FileTypeFilterIterator.phpZ[^Z(&'vendor/symfony/polyfill-ctype/Ctype.phpH [^H ݴ+vendor/symfony/polyfill-ctype/bootstrap.phpI[^ID!e+vendor/symfony/polyfill-php70/bootstrap.phpm[^mf'vendor/symfony/polyfill-php70/Php70.php[^=+@vendor/symfony/polyfill-php70/Resources/stubs/AssertionError.php.[^.&8yAvendor/symfony/polyfill-php70/Resources/stubs/ArithmeticError.php/[^/|mʹXvendor/symfony/polyfill-php70/Resources/stubs/SessionUpdateTimestampHandlerInterface.php[^~;vendor/symfony/polyfill-php70/Resources/stubs/TypeError.php)[^)O_Evendor/symfony/polyfill-php70/Resources/stubs/DivisionByZeroError.php3[^3h;<vendor/symfony/polyfill-php70/Resources/stubs/ParseError.php*[^*ᤴ7vendor/symfony/polyfill-php70/Resources/stubs/Error.php)[^)[k &vendor/symfony/stopwatch/Stopwatch.php[^#i{+vendor/symfony/stopwatch/StopwatchEvent.php [^ !A $vendor/symfony/stopwatch/Section.php[^-,vendor/symfony/stopwatch/StopwatchPeriod.phpp[^pΧ!+vendor/symfony/options-resolver/Options.php{[^{|3vendor/symfony/options-resolver/OptionsResolver.phpo9[^o9REvendor/symfony/options-resolver/Exception/MissingOptionsException.php[^=Gvendor/symfony/options-resolver/Exception/OptionDefinitionException.php[^@4=vendor/symfony/options-resolver/Exception/AccessException.php[^5Gvendor/symfony/options-resolver/Exception/UndefinedOptionsException.php[^p ͐Fvendor/symfony/options-resolver/Exception/NoConfigurationException.php[^jE@vendor/symfony/options-resolver/Exception/ExceptionInterface.phpn[^nyEvendor/symfony/options-resolver/Exception/InvalidOptionsException.php[^k Fvendor/symfony/options-resolver/Exception/InvalidArgumentException.php[^GCvendor/symfony/options-resolver/Exception/NoSuchOptionException.php[^g]ȴEvendor/symfony/options-resolver/Debug/OptionsResolverIntrospector.php[^ܴ)vendor/symfony/filesystem/LockHandler.php6[^6|u4(vendor/symfony/filesystem/Filesystem.php:[^:д<vendor/symfony/filesystem/Exception/IOExceptionInterface.php[^jwM=vendor/symfony/filesystem/Exception/FileNotFoundException.php[^9`:vendor/symfony/filesystem/Exception/ExceptionInterface.phpi[^i$ 3vendor/symfony/filesystem/Exception/IOException.php[^\5<vendor/symfony/event-dispatcher/EventSubscriberInterface.php[^ҘAvendor/symfony/event-dispatcher/ContainerAwareEventDispatcher.php[^,JKvendor/symfony/event-dispatcher/Debug/TraceableEventDispatcherInterface.php5[^5fVBvendor/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php[^O '9vendor/symfony/event-dispatcher/Debug/WrappedListener.php [^ !0vendor/symfony/event-dispatcher/GenericEvent.phpl[^l@F^ĴMvendor/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php[^\])vendor/symfony/event-dispatcher/Event.php:[^:E <vendor/symfony/event-dispatcher/ImmutableEventDispatcher.phpq[^q<vendor/symfony/event-dispatcher/EventDispatcherInterface.php[^pX3vendor/symfony/event-dispatcher/EventDispatcher.php3[^3Krx"&vendor/symfony/console/Application.phpLg[^Lg _+/vendor/symfony/console/Logger/ConsoleLogger.php [^  Z/vendor/symfony/console/Output/ConsoleOutput.php[^1vendor/symfony/console/Output/OutputInterface.php[^n (vendor/symfony/console/Output/Output.php [^ 0p.vendor/symfony/console/Output/StreamOutput.php[^^Ӵ,vendor/symfony/console/Output/NullOutput.php[^Z8vendor/symfony/console/Output/ConsoleOutputInterface.php[^ʴ0vendor/symfony/console/Output/BufferedOutput.php_[^_>P6vendor/symfony/console/Helper/DebugFormatterHelper.phpj[^jOG 7vendor/symfony/console/Helper/SymfonyQuestionHelper.php [^ ~S'vendor/symfony/console/Helper/Table.php-[^-0vendor/symfony/console/Helper/TableSeparator.php[^2vendor/symfony/console/Helper/DescriptorHelper.phpe[^e2_,vendor/symfony/console/Helper/TableStyle.php' [^' gb!2vendor/symfony/console/Helper/InputAwareHelper.phpc[^c1vendor/symfony/console/Helper/FormatterHelper.phpD[^D+L+vendor/symfony/console/Helper/HelperSet.phpf[^fx0vendor/symfony/console/Helper/QuestionHelper.php$[^$G /vendor/symfony/console/Helper/ProcessHelper.phpQ [^Q ;B1vendor/symfony/console/Helper/HelperInterface.php[^+vendor/symfony/console/Helper/TableCell.phpv[^v+ (vendor/symfony/console/Helper/Helper.phpk[^k'颴3vendor/symfony/console/Helper/ProgressIndicator.phpD[^DJxF-vendor/symfony/console/Helper/ProgressBar.php7%[^7%A<2vendor/symfony/console/Question/ChoiceQuestion.phpB [^B д8vendor/symfony/console/Question/ConfirmationQuestion.php[^d?,vendor/symfony/console/Question/Question.php [^ /vendor/symfony/console/Tester/CommandTester.php[^3vendor/symfony/console/Tester/ApplicationTester.php' [^' a廴3vendor/symfony/console/Exception/LogicException.php[^O\e;vendor/symfony/console/Exception/InvalidOptionException.php[^H=vendor/symfony/console/Exception/CommandNotFoundException.php[^K*57vendor/symfony/console/Exception/ExceptionInterface.phpf[^fAB5vendor/symfony/console/Exception/RuntimeException.php[^,6=vendor/symfony/console/Exception/InvalidArgumentException.php[^̽Z/vendor/symfony/console/Style/StyleInterface.php[^,vendor/symfony/console/Style/OutputStyle.phpW[^W?-vendor/symfony/console/Style/SymfonyStyle.phpY[^Y6vendor/symfony/console/EventListener/ErrorListener.php"[^"M^ .vendor/symfony/console/Command/ListCommand.php[^5-*vendor/symfony/console/Command/Command.php;![^;!~0vendor/symfony/console/Command/LockableTrait.php2[^2Nߴ.vendor/symfony/console/Command/HelpCommand.php[^<Dvendor/symfony/console/DependencyInjection/AddConsoleCommandPass.phpb [^b 蒜#vendor/symfony/console/Terminal.phps [^s ;iy(vendor/symfony/console/ConsoleEvents.php[^'Bvendor/symfony/console/Formatter/OutputFormatterStyleInterface.php[^G9vendor/symfony/console/Formatter/OutputFormatterStyle.php[^`*=vendor/symfony/console/Formatter/OutputFormatterInterface.php[^4vendor/symfony/console/Formatter/OutputFormatter.php[^LV!>vendor/symfony/console/Formatter/OutputFormatterStyleStack.php;[^;%4vendor/symfony/console/Descriptor/JsonDescriptor.php[^vf{9vendor/symfony/console/Descriptor/DescriptorInterface.php[^P~<vendor/symfony/console/Descriptor/ApplicationDescription.php [^ @?,״3vendor/symfony/console/Descriptor/XmlDescriptor.php [^ 4 δ4vendor/symfony/console/Descriptor/TextDescriptor.php![^!uD0vendor/symfony/console/Descriptor/Descriptor.phps[^scBM8vendor/symfony/console/Descriptor/MarkdownDescriptor.php[^>4&vendor/symfony/console/Input/Input.phpG [^G 0vendor/symfony/console/Input/InputDefinition.php[^B"퀴+vendor/symfony/console/Input/ArrayInput.php+ [^+ 'ٴ,vendor/symfony/console/Input/InputOption.php [^ 9vendor/symfony/console/Input/StreamableInputInterface.php[^B/vendor/symfony/console/Input/InputInterface.php[^rrg.vendor/symfony/console/Input/InputArgument.php [^ ;,vendor/symfony/console/Input/StringInput.phpW[^W}j*vendor/symfony/console/Input/ArgvInput.php[^ 4vendor/symfony/console/Input/InputAwareInterface.php[^O?vendor/symfony/console/CommandLoader/ContainerCommandLoader.phpH[^H-ߴ=vendor/symfony/console/CommandLoader/FactoryCommandLoader.php[^m?vendor/symfony/console/CommandLoader/CommandLoaderInterface.phpG[^G?4vendor/symfony/console/Event/ConsoleCommandEvent.php[^!ȴ6vendor/symfony/console/Event/ConsoleExceptionEvent.php[^*\ȴ-vendor/symfony/console/Event/ConsoleEvent.php[^Ӵ2vendor/symfony/console/Event/ConsoleErrorEvent.phpb[^bMh6vendor/symfony/console/Event/ConsoleTerminateEvent.phpz[^z,L-vendor/symfony/polyfill-mbstring/Mbstring.phpG[^Gɖз.vendor/symfony/polyfill-mbstring/bootstrap.php[^;@vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php@[^@شFvendor/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php[^y_@vendor/symfony/polyfill-mbstring/Resources/unidata/upperCase.phpfA[^fAfߴ'vendor/symfony/polyfill-php72/Php72.php[^ۆ6o+vendor/symfony/polyfill-php72/bootstrap.phpI[^I"-vendor/symfony/process/Pipes/WindowsPipes.php [^ Yp"*vendor/symfony/process/Pipes/UnixPipes.php[^#̴/vendor/symfony/process/Pipes/PipesInterface.phpm[^mu.vendor/symfony/process/Pipes/AbstractPipes.php [^ ym'vendor/symfony/process/ProcessUtils.php7[^7~+vendor/symfony/process/ExecutableFinder.php[^c@v%vendor/symfony/process/PhpProcess.php[^xp3vendor/symfony/process/Exception/LogicException.php[^ ;vendor/symfony/process/Exception/ProcessFailedException.phpx[^xzy7vendor/symfony/process/Exception/ExceptionInterface.phpf[^f]>T=vendor/symfony/process/Exception/ProcessTimedOutException.php[^5vendor/symfony/process/Exception/RuntimeException.php[^:=vendor/symfony/process/Exception/InvalidArgumentException.php[^+_&vendor/symfony/process/InputStream.php'[^'X>.vendor/symfony/process/PhpExecutableFinder.php[^b"vendor/symfony/process/Process.phpl[^lѯ)vendor/symfony/process/ProcessBuilder.php [^ AeѴ:vendor/paragonie/random_compat/lib/random_bytes_mcrypt.php5[^5IWyl1vendor/paragonie/random_compat/lib/random_int.php.[^.m1=vendor/paragonie/random_compat/lib/random_bytes_libsodium.php[^3+2g-vendor/paragonie/random_compat/lib/random.php [^ 5vendor/paragonie/random_compat/lib/error_polyfill.php[^a4>vendor/paragonie/random_compat/lib/random_bytes_com_dotnet.php[^vA?vendor/paragonie/random_compat/lib/random_bytes_dev_urandom.php[^8vendor/paragonie/random_compat/lib/byte_safe_strings.phpp[^p36dDvendor/paragonie/random_compat/lib/random_bytes_libsodium_legacy.php[^!2vendor/paragonie/random_compat/lib/cast_to_int.php[^|w%vendor/composer/autoload_classmap.php[^;-vendor/composer/xdebug-handler/src/Status.php [^ $v-0vendor/composer/xdebug-handler/src/PhpConfig.php[^*D4vendor/composer/xdebug-handler/src/XdebugHandler.php:![^:!U .vendor/composer/xdebug-handler/src/Process.php [^ 5ʴ#vendor/composer/autoload_static.php*[^*k̼vendor/composer/ClassLoader.php[^IlR!vendor/composer/autoload_psr4.php[^"vendor/composer/autoload_files.php5[^5|p!vendor/composer/autoload_real.php[^`մ,vendor/composer/semver/src/VersionParser.php*[^*GL%vendor/composer/semver/src/Semver.php[^?nݴ4vendor/composer/semver/src/Constraint/Constraint.php [^ I&9vendor/composer/semver/src/Constraint/MultiConstraint.php,[^,0==vendor/composer/semver/src/Constraint/ConstraintInterface.php[^0C,9vendor/composer/semver/src/Constraint/EmptyConstraint.php[^<vendor/composer/semver/src/Constraint/AbstractConstraint.php[^խw-)vendor/composer/semver/src/Comparator.php[^wl'vendor/composer/autoload_namespaces.phpd[^dZHvendor/autoload.php[^FWvendor/php-cs-fixer/diff/src/GeckoPackages/DiffOutputBuilder/ConfigurationException.php[^z8Yvendor/php-cs-fixer/diff/src/GeckoPackages/DiffOutputBuilder/UnifiedDiffOutputBuilder.php[^@:L*vendor/php-cs-fixer/diff/src/v3_0/Diff.php[^aFGvendor/php-cs-fixer/diff/src/v3_0/Output/DiffOutputBuilderInterface.php[^R8OGvendor/php-cs-fixer/diff/src/v3_0/Output/AbstractChunkOutputBuilder.php[^=hBvendor/php-cs-fixer/diff/src/v3_0/Output/DiffOnlyOutputBuilder.php[^7Kvendor/php-cs-fixer/diff/src/v3_0/Output/StrictUnifiedDiffOutputBuilder.php[^}Evendor/php-cs-fixer/diff/src/v3_0/Output/UnifiedDiffOutputBuilder.php|[^|o,vendor/php-cs-fixer/diff/src/v3_0/Differ.php[^ F*Uvendor/php-cs-fixer/diff/src/v3_0/TimeEfficientLongestCommonSubsequenceCalculator.php[^#1,vendor/php-cs-fixer/diff/src/v3_0/Parser.php[^j?Hvendor/php-cs-fixer/diff/src/v3_0/LongestCommonSubsequenceCalculator.php[^kbFvendor/php-cs-fixer/diff/src/v3_0/Exception/ConfigurationException.php[^ks9vendor/php-cs-fixer/diff/src/v3_0/Exception/Exception.phpH[^H{nHvendor/php-cs-fixer/diff/src/v3_0/Exception/InvalidArgumentException.php[^+vendor/php-cs-fixer/diff/src/v3_0/Chunk.php[^&_F*vendor/php-cs-fixer/diff/src/v3_0/Line.php[^8Wvendor/php-cs-fixer/diff/src/v3_0/MemoryEfficientLongestCommonSubsequenceCalculator.php [^ _*vendor/php-cs-fixer/diff/src/v2_0/Diff.php[^Gvendor/php-cs-fixer/diff/src/v2_0/Output/DiffOutputBuilderInterface.php[^uGvendor/php-cs-fixer/diff/src/v2_0/Output/AbstractChunkOutputBuilder.php[^1%|Bvendor/php-cs-fixer/diff/src/v2_0/Output/DiffOnlyOutputBuilder.php[^E{IEvendor/php-cs-fixer/diff/src/v2_0/Output/UnifiedDiffOutputBuilder.php [^ .۴,vendor/php-cs-fixer/diff/src/v2_0/Differ.php+[^+N1 δUvendor/php-cs-fixer/diff/src/v2_0/TimeEfficientLongestCommonSubsequenceCalculator.php[^K,vendor/php-cs-fixer/diff/src/v2_0/Parser.php[^lCHvendor/php-cs-fixer/diff/src/v2_0/LongestCommonSubsequenceCalculator.php[^59vendor/php-cs-fixer/diff/src/v2_0/Exception/Exception.phpH[^Hs-Hvendor/php-cs-fixer/diff/src/v2_0/Exception/InvalidArgumentException.php[^;$+vendor/php-cs-fixer/diff/src/v2_0/Chunk.php[^k˴*vendor/php-cs-fixer/diff/src/v2_0/Line.php[^AQWvendor/php-cs-fixer/diff/src/v2_0/MemoryEfficientLongestCommonSubsequenceCalculator.php [^ h *vendor/php-cs-fixer/diff/src/v1_4/Diff.php[^0}n]vendor/php-cs-fixer/diff/src/v1_4/LCS/TimeEfficientLongestCommonSubsequenceImplementation.php[^ 8_vendor/php-cs-fixer/diff/src/v1_4/LCS/MemoryEfficientLongestCommonSubsequenceImplementation.php[^:Bvendor/php-cs-fixer/diff/src/v1_4/LCS/LongestCommonSubsequence.php[^),vendor/php-cs-fixer/diff/src/v1_4/Differ.phpa[^aB,vendor/php-cs-fixer/diff/src/v1_4/Parser.php[^6qc+vendor/php-cs-fixer/diff/src/v1_4/Chunk.php [^ ۴*vendor/php-cs-fixer/diff/src/v1_4/Line.php[^!/%src/Linter/TokenizerLintingResult.php[^+)src/Linter/ProcessLinter.php1 [^1 + src/Linter/Linter.php8[^8Xϴ%src/Linter/LintingResultInterface.phps[^sO:Nsrc/Linter/LintingException.phpg[^gK)src/Linter/UnavailableLinterException.phps[^sGɴ*src/Linter/ProcessLinterProcessBuilder.phpR[^RC#src/Linter/ProcessLintingResult.php[^yȴsrc/Linter/TokenizerLinter.php[^Ĵsrc/Linter/CachingLinter.php[^lnsrc/Linter/LinterInterface.php[^/Xƴsrc/FileRemoval.phpb[^bTsrc/Console/Application.php [^ sX0src/Console/SelfUpdate/GithubClientInterface.php[^]δ'src/Console/SelfUpdate/GithubClient.php[^99⌴5src/Console/SelfUpdate/NewVersionCheckerInterface.php[^,Kl,src/Console/SelfUpdate/NewVersionChecker.phpy[^yI"src/Console/Output/ErrorOutput.php [^ y=4-src/Console/Output/ProcessOutputInterface.php~[^~Ճ$src/Console/Output/ProcessOutput.php [^ Y!src/Console/Output/NullOutput.php[^~|U%src/Console/ConfigurationResolver.phpD[^DL5 src/Console/WarningsDetector.php[^/)src/Console/Command/SelfUpdateCommand.php[^0}"src/Console/Command/FixCommand.phpO[^O#src/Console/Command/HelpCommand.php^D[^^DLdŴ5src/Console/Command/DescribeNameNotFoundException.php[^6src/Console/Command/FixCommandExitStatusCalculator.php;[^;#'src/Console/Command/DescribeCommand.php&[^&`l%src/Console/Command/ReadmeCommand.php[^Bݴsrc/Config.php0 [^0 src/FixerNameValidator.php[^Rsrc/RuleSet.php0[^0Ncsrc/Tokenizer/Transformers.php[^b3J%src/Tokenizer/AbstractTransformer.php{[^{t src/Tokenizer/CodeHasher.php[^VJ0src/Tokenizer/Resolver/TypeShortNameResolver.php\[^\-bsrc/Tokenizer/CT.php[^0src/Tokenizer/Analyzer/NamespaceUsesAnalyzer.php[^꼴-src/Tokenizer/Analyzer/NamespacesAnalyzer.php[^bĴ8src/Tokenizer/Analyzer/Analysis/NamespaceUseAnalysis.php[^ˆ5src/Tokenizer/Analyzer/Analysis/NamespaceAnalysis.php [^ 7>src/Tokenizer/Analyzer/Analysis/StartEndTokenAwareAnalysis.php[^`90src/Tokenizer/Analyzer/Analysis/TypeAnalysis.php[^],D 4src/Tokenizer/Analyzer/Analysis/ArgumentAnalysis.phpA[^A1WX,src/Tokenizer/Analyzer/ArgumentsAnalyzer.php [^ ",src/Tokenizer/Analyzer/FunctionsAnalyzer.php[^,4~+src/Tokenizer/Analyzer/CommentsAnalyzer.php[^Դ)src/Tokenizer/Analyzer/BlocksAnalyzer.php![^!=2src/Tokenizer/Transformer/ReturnRefTransformer.php[^ u6src/Tokenizer/Transformer/ClassConstantTransformer.php[^8src/Tokenizer/Transformer/TypeAlternationTransformer.phpi[^i9/src/Tokenizer/Transformer/ImportTransformer.php#[^# P@src/Tokenizer/Transformer/BraceClassInstantiationTransformer.php[^K ;,src/Tokenizer/Transformer/UseTransformer.php[^;є4src/Tokenizer/Transformer/SquareBraceTransformer.php [^ Jv6src/Tokenizer/Transformer/ArrayTypehintTransformer.php[^q:src/Tokenizer/Transformer/NamespaceOperatorTransformer.php[^t3src/Tokenizer/Transformer/CurlyBraceTransformer.phpo[^oNM:src/Tokenizer/Transformer/WhitespacyCommentTransformer.php[^嬆2src/Tokenizer/Transformer/TypeColonTransformer.php[^a%O55src/Tokenizer/Transformer/NullableTypeTransformer.phpL[^LI& src/Tokenizer/TokensAnalyzer.phpd2[^d2src/Tokenizer/Tokens.phpR[^RV5:src/Tokenizer/Generator/NamespacedStringTokenGenerator.php[^݊j&src/Tokenizer/TransformerInterface.phpN[^Nsrc/Tokenizer/Token.php![^!Ϳ src/AbstractPhpdocTypesFixer.php[^ܝ)src/AbstractLinesBeforeNamespaceFixer.phpA[^AyKMsrc/AbstractFopenFlagFixer.php[^'aдsrc/PharChecker.php[^C"}Ŵsrc/Finder.php([^(src/ConfigInterface.phpe[^e**=src/AbstractFixer.php[^&src/FileReader.php[^1src/StdinFileInfo.php#[^#`)D"src/AbstractNoUselessElseFixer.php [^ ̴src/ToolInfoInterface.phpB[^B-Wsrc/PregException.phpg[^g[^>9src/Runner/Runner.php[^柴)src/Runner/FileCachingLintingIterator.php[^ev +src/FixerDefinition/CodeSampleInterface.php[^4/:src/FixerDefinition/VersionSpecificCodeSampleInterface.php[^l1src/FixerDefinition/VersionSpecificCodeSample.php[^$NW5src/FixerDefinition/VersionSpecificationInterface.php[^ݬ,src/FixerDefinition/VersionSpecification.phpm[^m0E 0src/FixerDefinition/FixerDefinitionInterface.phpb[^b4/"src/FixerDefinition/CodeSample.php[^M .src/FixerDefinition/FileSpecificCodeSample.phph[^huϋ7src/FixerDefinition/FileSpecificCodeSampleInterface.php[^vO'src/FixerDefinition/FixerDefinition.php[^z=#src/AbstractPsrAutoloadingFixer.php![^!$ij:#src/Cache/CacheManagerInterface.php[^Dsrc/Cache/CacheInterface.php[^'뭴src/Cache/NullCacheManager.php[^4nsrc/Cache/FileHandler.phpL[^LO~ src/Cache/SignatureInterface.php4[^4Jsrc/Cache/FileCacheManager.php[^src/Cache/Directory.phpP[^PTsrc/Cache/Signature.php{[^{#R src/Cache/DirectoryInterface.php[^Fd"src/Cache/FileHandlerInterface.php[^hsrc/Cache/Cache.php*[^*oܴsrc/WordMatcher.php[^x'src/AbstractDoctrineAnnotationFixer.phph [^h Hrsrc/Fixer/Basic/BracesFixer.php`[^` Rsrc/Fixer/Basic/Psr0Fixer.php[^X"Ǵ.src/Fixer/Basic/NonPrintableCharacterFixer.php[^_'src/Fixer/Basic/Psr4Fixer.php*[^*Oʹ!src/Fixer/Basic/EncodingFixer.php;[^;xAШ)src/Fixer/Comment/NoEmptyCommentFixer.php [^ tw9src/Fixer/Comment/MultilineCommentOpeningClosingFixer.php[^Q1src/Fixer/Comment/SingleLineCommentStyleFixer.php [^ Wn8src/Fixer/Comment/NoTrailingWhitespaceInCommentFixer.php[^Ś(src/Fixer/Comment/HeaderCommentFixer.phpB'[^B'V-src/Fixer/Comment/HashToSlashCommentFixer.php[^MZ`*src/Fixer/Comment/CommentToPhpdocFixer.php [^ 34src/Fixer/Phpdoc/PhpdocAnnotationWithoutDotFixer.phpu [^u .=%src/Fixer/Phpdoc/PhpdocTypesFixer.php[^G:7src/Fixer/Phpdoc/GeneralPhpdocAnnotationRemoveFixer.php[^͛q)src/Fixer/Phpdoc/PhpdocInlineTagFixer.phpk[^ky/src/Fixer/Phpdoc/AlignMultilineCommentFixer.phpH [^H w*src/Fixer/Phpdoc/PhpdocNoAliasTagFixer.phpX [^X mC.src/Fixer/Phpdoc/PhpdocVarWithoutNameFixer.phpb [^b DL*src/Fixer/Phpdoc/PhpdocTypesOrderFixer.php[^.IrBsrc/Fixer/Phpdoc/PhpdocTrimConsecutiveBlankLineSeparationFixer.php [^ 'src/Fixer/Phpdoc/PhpdocSummaryFixer.php3[^3}x3src/Fixer/Phpdoc/PhpdocReturnSelfReferenceFixer.php[^*Ց%src/Fixer/Phpdoc/PhpdocOrderFixer.php3 [^3 8`&src/Fixer/Phpdoc/PhpdocIndentFixer.php/[^/1,]^3src/Fixer/Phpdoc/PhpdocNoUselessInheritdocFixer.php [^ %e4src/Fixer/Phpdoc/PhpdocSingleLineVarSpacingFixer.php)[^) ´&src/Fixer/Phpdoc/PhpdocScalarFixer.phpD[^Dk*src/Fixer/Phpdoc/PhpdocSeparationFixer.php] [^] tj1src/Fixer/Phpdoc/NoSuperfluousPhpdocTagsFixer.php"[^"'src/Fixer/Phpdoc/NoEmptyPhpdocFixer.phpu[^uDlA-src/Fixer/Phpdoc/PhpdocNoEmptyReturnFixer.php[^eJV)src/Fixer/Phpdoc/PhpdocNoPackageFixer.php[^9)src/Fixer/Phpdoc/PhpdocToCommentFixer.php%[^%9src/Fixer/Phpdoc/PhpdocAddMissingParamAnnotationFixer.php[^´%src/Fixer/Phpdoc/PhpdocAlignFixer.php[^Mz1src/Fixer/Phpdoc/NoBlankLinesAfterPhpdocFixer.php[^9src/Fixer/Phpdoc/PhpdocVarAnnotationCorrectOrderFixer.php[^P $src/Fixer/Phpdoc/PhpdocTrimFixer.phpo[^o(src/Fixer/Phpdoc/PhpdocNoAccessFixer.php[^ lH<src/Fixer/ConstantNotation/NativeConstantInvocationFixer.php[^|&/&src/Fixer/PhpTag/NoClosingTagFixer.php[^ +(src/Fixer/PhpTag/NoShortEchoTagFixer.php.[^.їI2src/Fixer/PhpTag/LinebreakAfterOpeningTagFixer.php[^.t(src/Fixer/PhpTag/FullOpeningTagFixer.phpL[^LE2src/Fixer/PhpTag/BlankLineAfterOpeningTagFixer.php<[^<+src/Fixer/Casing/MagicMethodCasingFixer.php[^C}V=src/Fixer/Casing/NativeFunctionTypeDeclarationCasingFixer.php [^ rW+src/Fixer/Casing/LowercaseKeywordsFixer.phpM[^M 2src/Fixer/Casing/LowercaseStaticReferenceFixer.php[^B-src/Fixer/Casing/MagicConstantCasingFixer.php>[^>6ݴ,src/Fixer/Casing/LowercaseConstantsFixer.php[^7u#.src/Fixer/Casing/NativeFunctionCasingFixer.php[^hz3),src/Fixer/Operator/LogicalOperatorsFixer.phpQ[^Qh)src/Fixer/Operator/NewWithBracesFixer.php [^ b˴1src/Fixer/Operator/TernaryOperatorSpacesFixer.phpc[^cy*src/Fixer/Operator/IncrementStyleFixer.php[^厴0src/Fixer/Operator/StandardizeNotEqualsFixer.php5[^5GΫ0src/Fixer/Operator/NotOperatorWithSpaceFixer.php,[^,GƦ0src/Fixer/Operator/StandardizeIncrementFixer.phpD [^D W!0src/Fixer/Operator/BinaryOperatorSpacesFixer.phpfC[^fC/+t3src/Fixer/Operator/TernaryToNullCoalescingFixer.php/[^/qt;src/Fixer/Operator/ObjectOperatorWithoutWhitespaceFixer.php[^t˴'src/Fixer/Operator/ConcatSpaceFixer.php{ [^{ Y7-src/Fixer/Operator/AlignEqualsFixerHelper.php[^4IUR/src/Fixer/Operator/UnaryOperatorSpacesFixer.php[^2src/Fixer/Operator/AlignDoubleArrowFixerHelper.phpg [^g L=(src/Fixer/Operator/PreIncrementFixer.php[^9src/Fixer/Operator/NotOperatorWithSuccessorSpaceFixer.php[^}-src/Fixer/Semicolon/NoEmptyStatementFixer.php [^ ڒRCsrc/Fixer/Semicolon/NoSinglelineWhitespaceBeforeSemicolonsFixer.php,[^,aܴ@src/Fixer/Semicolon/MultilineWhitespaceBeforeSemicolonsFixer.php.[^.3)6src/Fixer/Semicolon/SemicolonAfterInstructionFixer.php[^_k0src/Fixer/Semicolon/SpaceAfterSemicolonFixer.php [^ UJ5Bsrc/Fixer/Semicolon/NoMultilineWhitespaceBeforeSemicolonsFixer.php[^.,src/Fixer/Strict/DeclareStrictTypesFixer.phpw [^w Rw%src/Fixer/Strict/StrictParamFixer.phpC [^C "˲*src/Fixer/Strict/StrictComparisonFixer.phpt[^tc7src/Fixer/ReturnNotation/BlankLineBeforeReturnFixer.php[^Gї6src/Fixer/ReturnNotation/SimplifiedNullReturnFixer.php [^ HNEC2src/Fixer/ReturnNotation/ReturnAssignmentFixer.phpa[^a31src/Fixer/ReturnNotation/NoUselessReturnFixer.phpp[^pR|х#src/Fixer/DefinedFixerInterface.php[^ܴ=src/Fixer/ClassNotation/NoNullPropertyInitializationFixer.php[^nʹ+src/Fixer/ClassNotation/FinalClassFixer.phpU[^UEh?src/Fixer/ClassNotation/SingleClassElementPerStatementFixer.phpi[^i~y{6src/Fixer/ClassNotation/NoUnneededFinalMethodFixer.php[^k:src/Fixer/ClassNotation/ClassAttributesSeparationFixer.php [^ *">src/Fixer/ClassNotation/SingleTraitInsertPerStatementFixer.php[^ δ>src/Fixer/ClassNotation/NoBlankLinesAfterClassOpeningFixer.php[^`k0src/Fixer/ClassNotation/ClassDefinitionFixer.php)[^)f2src/Fixer/ClassNotation/OrderedInterfacesFixer.php[^363src/Fixer/ClassNotation/ProtectedToPrivateFixer.phph[^hF2src/Fixer/ClassNotation/NoPhp4ConstructorFixer.php[^Ty 5src/Fixer/ClassNotation/OrderedClassElementsFixer.phpk%[^k%`a-src/Fixer/ClassNotation/SelfAccessorFixer.php[^1Ҵ1src/Fixer/ClassNotation/MethodSeparationFixer.php[^Qx3src/Fixer/ClassNotation/VisibilityRequiredFixer.php[^ 3src/Fixer/ClassNotation/FinalInternalClassFixer.php [^  (src/Fixer/ConfigurableFixerInterface.php[^/src/Fixer/ClassUsage/DateTimeImmutableFixer.php [^ x-,src/Fixer/WhitespacesAwareFixerInterface.php[^gs&src/Fixer/DeprecatedFixerInterface.php[^=rT-src/Fixer/StringNotation/SingleQuoteFixer.php,[^,71src/Fixer/StringNotation/HeredocToNowdocFixer.phps[^so\ٴ2src/Fixer/StringNotation/StringLineEndingFixer.php9[^9k u0src/Fixer/StringNotation/NoBinaryStringFixer.php[^9t;src/Fixer/StringNotation/EscapeImplicitBackslashesFixer.php[^1?src/Fixer/StringNotation/SimpleToComplexStringVariableFixer.php[^8src/Fixer/StringNotation/ExplicitStringVariableFixer.php[^?*src/Fixer/ListNotation/ListSyntaxFixer.php [^ #Χ>src/Fixer/DoctrineAnnotation/DoctrineAnnotationBracesFixer.php [^ G:s>src/Fixer/DoctrineAnnotation/DoctrineAnnotationSpacesFixer.php"[^")Gsrc/Fixer/DoctrineAnnotation/DoctrineAnnotationArrayAssignmentFixer.php[^mE/Csrc/Fixer/DoctrineAnnotation/DoctrineAnnotationIndentationFixer.php%[^%w7src/Fixer/Whitespace/NoSpacesInsideParenthesisFixer.php/[^/$-src/Fixer/Whitespace/IndentationTypeFixer.php [^ (`W6src/Fixer/Whitespace/BlankLineBeforeStatementFixer.phpb[^b]a2src/Fixer/Whitespace/NoSpacesAroundOffsetFixer.phpY [^Y nA(src/Fixer/Whitespace/LineEndingFixer.php[^ 5src/Fixer/Whitespace/NoWhitespaceInBlankLineFixer.php[^tδ2src/Fixer/Whitespace/NoTrailingWhitespaceFixer.php[^Fi:src/Fixer/Whitespace/NoExtraConsecutiveBlankLinesFixer.php;[^;Sд/src/Fixer/Whitespace/NoExtraBlankLinesFixer.php![^!N;7src/Fixer/Whitespace/MethodChainingIndentationFixer.phpH[^HY5src/Fixer/Whitespace/CompactNullableTypehintFixer.php[^ig:.src/Fixer/Whitespace/ArrayIndentationFixer.php![^!&޴0src/Fixer/Whitespace/HeredocIndentationFixer.phpT [^T ț92src/Fixer/Whitespace/SingleBlankLineAtEofFixer.php[^ ^ECsrc/Fixer/NamespaceNotation/SingleBlankLineBeforeNamespaceFixer.php[^4$@src/Fixer/NamespaceNotation/NoBlankLinesBeforeNamespaceFixer.php[^uAsrc/Fixer/NamespaceNotation/NoLeadingNamespaceWhitespaceFixer.phpA[^AmJ<src/Fixer/NamespaceNotation/BlankLineAfterNamespaceFixer.php [^ |5src/Fixer/CastNotation/ModernizeTypesCastingFixer.php [^ m-src/Fixer/CastNotation/LowercaseCastFixer.php9[^9ϯ"/src/Fixer/CastNotation/NoShortBoolCastFixer.php[^'*src/Fixer/CastNotation/CastSpacesFixer.phpE[^Ed+src/Fixer/CastNotation/NoUnsetCastFixer.php[^Z2/src/Fixer/CastNotation/ShortScalarCastFixer.php[^t0src/Fixer/ArrayNotation/TrimArraySpacesFixer.php[^5,src/Fixer/ArrayNotation/ArraySyntaxFixer.phpV [^V ؆)Asrc/Fixer/ArrayNotation/NoTrailingCommaInSinglelineArrayFixer.php[^$쳴Gsrc/Fixer/ArrayNotation/NoMultilineWhitespaceAroundDoubleArrowFixer.php[^m;4src/Fixer/ArrayNotation/NormalizeIndexBraceFixer.php[^$ݓ?src/Fixer/ArrayNotation/NoWhitespaceBeforeCommaInArrayFixer.php [^ N>src/Fixer/ArrayNotation/TrailingCommaInMultilineArrayFixer.php [^ 3{-<src/Fixer/ArrayNotation/WhitespaceAfterCommaInArrayFixer.php[^*src/Fixer/Naming/NoHomoglyphNamesFixer.php [^ {˴src/Fixer/FixerInterface.php[^eus`6src/Fixer/LanguageConstruct/NoUnsetOnPropertyFixer.php[^B=src/Fixer/LanguageConstruct/CombineConsecutiveIssetsFixer.php [^ jދ0src/Fixer/LanguageConstruct/DirConstantFixer.php4 [^4 1/@+src/Fixer/LanguageConstruct/IsNullFixer.php[^pN=src/Fixer/LanguageConstruct/CombineConsecutiveUnsetsFixer.php [^ c=src/Fixer/LanguageConstruct/ExplicitIndirectVariableFixer.php[^Z#7src/Fixer/LanguageConstruct/ClassKeywordRemoveFixer.php[^OZ=src/Fixer/LanguageConstruct/SilencedDeprecationErrorFixer.php[^Q<7src/Fixer/LanguageConstruct/FunctionToConstantFixer.php[^5src/Fixer/LanguageConstruct/ErrorSuppressionFixer.php [^ t':src/Fixer/LanguageConstruct/DeclareEqualNormalizeFixer.php> [^> O2src/Fixer/Import/SingleImportPerStatementFixer.phpq[^qÂi)src/Fixer/Import/NoUnusedImportsFixer.php[^R93src/Fixer/Import/FullyQualifiedStrictTypesFixer.php! [^! .src/Fixer/Import/NoLeadingImportSlashFixer.php~[^~02ۯ0src/Fixer/Import/SingleLineAfterImportsFixer.php [^ <츴(src/Fixer/Import/OrderedImportsFixer.phpi)[^i)Nve-src/Fixer/ControlStructure/YodaStyleFixer.php3[^3bɻ=src/Fixer/ControlStructure/NoTrailingCommaInListCallFixer.php[^YN*src/Fixer/ControlStructure/ElseifFixer.phpF[^F'k7src/Fixer/ControlStructure/NoAlternativeSyntaxFixer.php[^Q^@src/Fixer/ControlStructure/NoUnneededControlParenthesesFixer.phpz[^z2src/Fixer/ControlStructure/NoBreakCommentFixer.php![^!S/>src/Fixer/ControlStructure/SwitchCaseSemicolonToColonFixer.php[^<ô1src/Fixer/ControlStructure/NoUselessElseFixer.php[^vϭ9src/Fixer/ControlStructure/NoUnneededCurlyBracesFixer.phpZ [^Z +X3src/Fixer/ControlStructure/SwitchCaseSpaceFixer.php[^=+src/Fixer/ControlStructure/IncludeFixer.php [^ j}7src/Fixer/ControlStructure/NoSuperfluousElseifFixer.php[^ 񍑴+src/Fixer/PhpUnit/PhpUnitSizeClassFixer.php[^Y0src/Fixer/PhpUnit/PhpUnitDedicateAssertFixer.php&[^&;P&src/Fixer/PhpUnit/PhpUnitMockFixer.phpK[^K^ԣ0src/Fixer/PhpUnit/PhpUnitFqcnAnnotationFixer.phpL[^L1-src/Fixer/PhpUnit/PhpUnitExpectationFixer.php[^quѴ(src/Fixer/PhpUnit/PhpUnitStrictFixer.php [^ `ș5src/Fixer/PhpUnit/PhpUnitMockShortWillReturnFixer.php[^$߭;src/Fixer/PhpUnit/PhpUnitTestCaseStaticMethodCallsFixer.phpt-[^t-9src/Fixer/PhpUnit/PhpUnitSetUpTearDownVisibilityFixer.php [^ / 7.src/Fixer/PhpUnit/PhpUnitMethodCasingFixer.php[^y+src/Fixer/PhpUnit/PhpUnitConstructFixer.php[^Y/src/Fixer/PhpUnit/PhpUnitInternalClassFixer.php[[^[H2"*src/Fixer/PhpUnit/PhpUnitTargetVersion.php[^?<src/Fixer/PhpUnit/PhpUnitDedicateAssertInternalTypeFixer.php[^/,src/Fixer/PhpUnit/PhpUnitNamespacedFixer.php[^d29src/Fixer/PhpUnit/PhpUnitNoExpectationAnnotationFixer.php[^҆>9src/Fixer/PhpUnit/PhpUnitTestClassRequiresCoversFixer.php [^ 6/src/Fixer/PhpUnit/PhpUnitOrderedCoversFixer.php[^KC)0src/Fixer/PhpUnit/PhpUnitTestAnnotationFixer.php)[^)W)src/Fixer/Alias/NoMixedEchoPrintFixer.php [^ J'src/Fixer/Alias/MbStrFunctionsFixer.php [^ a,src/Fixer/Alias/PowToExponentiationFixer.phpD[^DE#src/Fixer/Alias/EregToPregFixer.php [^ ^)src/Fixer/Alias/NoAliasFunctionsFixer.php[^A$+src/Fixer/Alias/RandomApiMigrationFixer.php[^2,src/Fixer/Alias/BacktickToShellExecFixer.php. [^. F=&src/Fixer/Alias/SetTypeToCastFixer.php[^3src/Fixer/ConfigurationDefinitionFixerInterface.php[^M㵴/src/Fixer/FunctionNotation/ImplodeCallFixer.php[ [^[ Y,8=src/Fixer/FunctionNotation/NoSpacesAfterFunctionNameFixer.php [^ :9´9src/Fixer/FunctionNotation/FunctionTypehintSpaceFixer.php[^Y9src/Fixer/FunctionNotation/ReturnTypeDeclarationFixer.php [^ NQ.src/Fixer/FunctionNotation/VoidReturnFixer.phpL[^LcJ(0src/Fixer/FunctionNotation/StaticLambdaFixer.php [^ 87src/Fixer/FunctionNotation/MethodArgumentSpaceFixer.php-[^-S.src/Fixer/FunctionNotation/FopenFlagsFixer.php0 [^0 +7src/Fixer/FunctionNotation/FunctionDeclarationFixer.php[^F#Esrc/Fixer/FunctionNotation/NoUnreachableDefaultArgumentValueFixer.phph[^hy42src/Fixer/FunctionNotation/FopenFlagOrderFixer.phpW[^Wd6src/Fixer/FunctionNotation/PhpdocToReturnTypeFixer.php[^D ô8src/Fixer/FunctionNotation/CombineNestedDirnameFixer.phpD[^Dʹ蓴<src/Fixer/FunctionNotation/NativeFunctionInvocationFixer.php2[^2AU&src/FixerConfiguration/FixerOption.php[^J--src/FixerConfiguration/AllowedValueSubset.php[^8/src/FixerConfiguration/FixerOptionInterface.php\[^\j,-src/FixerConfiguration/AliasedFixerOption.php[^y-src/FixerConfiguration/FixerOptionBuilder.php:[^:=.5src/FixerConfiguration/FixerConfigurationResolver.phpt[^ts4src/FixerConfiguration/AliasedFixerOptionBuilder.php[^*>src/FixerConfiguration/FixerConfigurationResolverInterface.php[^#8src/FixerConfiguration/InvalidOptionsForEnvException.php[^R0src/FixerConfiguration/DeprecatedFixerOption.php[^I=src/FixerConfiguration/FixerConfigurationResolverRootless.php![^!L99src/FixerConfiguration/DeprecatedFixerOptionInterface.php[^$src/Event/Event.php[^mݴci-integration.sh[^|U{LICENSES[^SRMS php-cs-fixern [^n ggdelegate = $reader; $this->cache = $cache; $this->debug = (boolean) $debug; } public function getClassAnnotations(ReflectionClass $class) { $cacheKey = $class->getName(); if (isset($this->loadedAnnotations[$cacheKey])) { return $this->loadedAnnotations[$cacheKey]; } if (false === ($annots = $this->fetchFromCache($cacheKey, $class))) { $annots = $this->delegate->getClassAnnotations($class); $this->saveToCache($cacheKey, $annots); } return $this->loadedAnnotations[$cacheKey] = $annots; } public function getClassAnnotation(ReflectionClass $class, $annotationName) { foreach ($this->getClassAnnotations($class) as $annot) { if ($annot instanceof $annotationName) { return $annot; } } return null; } public function getPropertyAnnotations(\ReflectionProperty $property) { $class = $property->getDeclaringClass(); $cacheKey = $class->getName().'$'.$property->getName(); if (isset($this->loadedAnnotations[$cacheKey])) { return $this->loadedAnnotations[$cacheKey]; } if (false === ($annots = $this->fetchFromCache($cacheKey, $class))) { $annots = $this->delegate->getPropertyAnnotations($property); $this->saveToCache($cacheKey, $annots); } return $this->loadedAnnotations[$cacheKey] = $annots; } public function getPropertyAnnotation(\ReflectionProperty $property, $annotationName) { foreach ($this->getPropertyAnnotations($property) as $annot) { if ($annot instanceof $annotationName) { return $annot; } } return null; } public function getMethodAnnotations(\ReflectionMethod $method) { $class = $method->getDeclaringClass(); $cacheKey = $class->getName().'#'.$method->getName(); if (isset($this->loadedAnnotations[$cacheKey])) { return $this->loadedAnnotations[$cacheKey]; } if (false === ($annots = $this->fetchFromCache($cacheKey, $class))) { $annots = $this->delegate->getMethodAnnotations($method); $this->saveToCache($cacheKey, $annots); } return $this->loadedAnnotations[$cacheKey] = $annots; } public function getMethodAnnotation(\ReflectionMethod $method, $annotationName) { foreach ($this->getMethodAnnotations($method) as $annot) { if ($annot instanceof $annotationName) { return $annot; } } return null; } public function clearLoadedAnnotations() { $this->loadedAnnotations = array(); } private function fetchFromCache($cacheKey, ReflectionClass $class) { if (($data = $this->cache->fetch($cacheKey)) !== false) { if (!$this->debug || $this->isCacheFresh($cacheKey, $class)) { return $data; } } return false; } private function saveToCache($cacheKey, $value) { $this->cache->save($cacheKey, $value); if ($this->debug) { $this->cache->save('[C]'.$cacheKey, time()); } } private function isCacheFresh($cacheKey, ReflectionClass $class) { if (null === $lastModification = $this->getLastModification($class)) { return true; } return $this->cache->fetch('[C]'.$cacheKey) >= $lastModification; } private function getLastModification(ReflectionClass $class) { $filename = $class->getFileName(); $parent = $class->getParentClass(); return max(array_merge( [$filename ? filemtime($filename) : 0], array_map([$this, 'getTraitLastModificationTime'], $class->getTraits()), array_map([$this, 'getLastModification'], $class->getInterfaces()), $parent ? [$this->getLastModification($parent)] : [] )); } private function getTraitLastModificationTime(ReflectionClass $reflectionTrait) { $fileName = $reflectionTrait->getFileName(); return max(array_merge( [$fileName ? filemtime($fileName) : 0], array_map([$this, 'getTraitLastModificationTime'], $reflectionTrait->getTraits()) )); } } $dirs) { if (strpos($class, $namespace) === 0) { $file = str_replace("\\", DIRECTORY_SEPARATOR, $class) . ".php"; if ($dirs === null) { if ($path = stream_resolve_include_path($file)) { require $path; return true; } } else { foreach((array)$dirs AS $dir) { if (is_file($dir . DIRECTORY_SEPARATOR . $file)) { require $dir . DIRECTORY_SEPARATOR . $file; return true; } } } } } foreach (self::$loaders AS $loader) { if (call_user_func($loader, $class) === true) { return true; } } return false; } } $value) { $this->$key = $value; } } public function __get($name) { throw new \BadMethodCallException( sprintf("Unknown property '%s' on annotation '%s'.", $name, get_class($this)) ); } public function __set($name, $value) { throw new \BadMethodCallException( sprintf("Unknown property '%s' on annotation '%s'.", $name, get_class($this)) ); } } getUseStatements(); } if (false === $filename = $class->getFileName()) { return array(); } $content = $this->getFileContent($filename, $class->getStartLine()); if (null === $content) { return array(); } $namespace = preg_quote($class->getNamespaceName()); $content = preg_replace('/^.*?(\bnamespace\s+' . $namespace . '\s*[;{].*)$/s', '\\1', $content); $tokenizer = new TokenParser('parseUseStatements($class->getNamespaceName()); return $statements; } private function getFileContent($filename, $lineNumber) { if ( ! is_file($filename)) { return null; } $content = ''; $lineCnt = 0; $file = new SplFileObject($filename); while (!$file->eof()) { if ($lineCnt++ == $lineNumber) { break; } $content .= $file->fgets(); } return $content; } } reader = $reader; $this->umask = $umask; if (!is_dir($cacheDir) && !@mkdir($cacheDir, 0777 & (~$this->umask), true)) { throw new \InvalidArgumentException(sprintf('The directory "%s" does not exist and could not be created.', $cacheDir)); } $this->dir = rtrim($cacheDir, '\\/'); $this->debug = $debug; } public function getClassAnnotations(\ReflectionClass $class) { if ( ! isset($this->classNameHashes[$class->name])) { $this->classNameHashes[$class->name] = sha1($class->name); } $key = $this->classNameHashes[$class->name]; if (isset($this->loadedAnnotations[$key])) { return $this->loadedAnnotations[$key]; } $path = $this->dir.'/'.strtr($key, '\\', '-').'.cache.php'; if (!is_file($path)) { $annot = $this->reader->getClassAnnotations($class); $this->saveCacheFile($path, $annot); return $this->loadedAnnotations[$key] = $annot; } if ($this->debug && (false !== $filename = $class->getFileName()) && filemtime($path) < filemtime($filename)) { @unlink($path); $annot = $this->reader->getClassAnnotations($class); $this->saveCacheFile($path, $annot); return $this->loadedAnnotations[$key] = $annot; } return $this->loadedAnnotations[$key] = include $path; } public function getPropertyAnnotations(\ReflectionProperty $property) { $class = $property->getDeclaringClass(); if ( ! isset($this->classNameHashes[$class->name])) { $this->classNameHashes[$class->name] = sha1($class->name); } $key = $this->classNameHashes[$class->name].'$'.$property->getName(); if (isset($this->loadedAnnotations[$key])) { return $this->loadedAnnotations[$key]; } $path = $this->dir.'/'.strtr($key, '\\', '-').'.cache.php'; if (!is_file($path)) { $annot = $this->reader->getPropertyAnnotations($property); $this->saveCacheFile($path, $annot); return $this->loadedAnnotations[$key] = $annot; } if ($this->debug && (false !== $filename = $class->getFilename()) && filemtime($path) < filemtime($filename)) { @unlink($path); $annot = $this->reader->getPropertyAnnotations($property); $this->saveCacheFile($path, $annot); return $this->loadedAnnotations[$key] = $annot; } return $this->loadedAnnotations[$key] = include $path; } public function getMethodAnnotations(\ReflectionMethod $method) { $class = $method->getDeclaringClass(); if ( ! isset($this->classNameHashes[$class->name])) { $this->classNameHashes[$class->name] = sha1($class->name); } $key = $this->classNameHashes[$class->name].'#'.$method->getName(); if (isset($this->loadedAnnotations[$key])) { return $this->loadedAnnotations[$key]; } $path = $this->dir.'/'.strtr($key, '\\', '-').'.cache.php'; if (!is_file($path)) { $annot = $this->reader->getMethodAnnotations($method); $this->saveCacheFile($path, $annot); return $this->loadedAnnotations[$key] = $annot; } if ($this->debug && (false !== $filename = $class->getFilename()) && filemtime($path) < filemtime($filename)) { @unlink($path); $annot = $this->reader->getMethodAnnotations($method); $this->saveCacheFile($path, $annot); return $this->loadedAnnotations[$key] = $annot; } return $this->loadedAnnotations[$key] = include $path; } private function saveCacheFile($path, $data) { if (!is_writable($this->dir)) { throw new \InvalidArgumentException(sprintf('The directory "%s" is not writable. Both, the webserver and the console user need access. You can manage access rights for multiple users with "chmod +a". If your system does not support this, check out the acl package.', $this->dir)); } $tempfile = tempnam($this->dir, uniqid('', true)); if (false === $tempfile) { throw new \RuntimeException(sprintf('Unable to create tempfile in directory: %s', $this->dir)); } @chmod($tempfile, 0666 & (~$this->umask)); $written = file_put_contents($tempfile, 'umask)); if (false === rename($tempfile, $path)) { @unlink($tempfile); throw new \RuntimeException(sprintf('Unable to rename %s to %s', $tempfile, $path)); } } public function getClassAnnotation(\ReflectionClass $class, $annotationName) { $annotations = $this->getClassAnnotations($class); foreach ($annotations as $annotation) { if ($annotation instanceof $annotationName) { return $annotation; } } return null; } public function getMethodAnnotation(\ReflectionMethod $method, $annotationName) { $annotations = $this->getMethodAnnotations($method); foreach ($annotations as $annotation) { if ($annotation instanceof $annotationName) { return $annotation; } } return null; } public function getPropertyAnnotation(\ReflectionProperty $property, $annotationName) { $annotations = $this->getPropertyAnnotations($property); foreach ($annotations as $annotation) { if ($annotation instanceof $annotationName) { return $annotation; } } return null; } public function clearLoadedAnnotations() { $this->loadedAnnotations = array(); } } delegate = $reader; } public function getClassAnnotations(\ReflectionClass $class) { $annotations = array(); foreach ($this->delegate->getClassAnnotations($class) as $annot) { $annotations[get_class($annot)] = $annot; } return $annotations; } public function getClassAnnotation(\ReflectionClass $class, $annotation) { return $this->delegate->getClassAnnotation($class, $annotation); } public function getMethodAnnotations(\ReflectionMethod $method) { $annotations = array(); foreach ($this->delegate->getMethodAnnotations($method) as $annot) { $annotations[get_class($annot)] = $annot; } return $annotations; } public function getMethodAnnotation(\ReflectionMethod $method, $annotation) { return $this->delegate->getMethodAnnotation($method, $annotation); } public function getPropertyAnnotations(\ReflectionProperty $property) { $annotations = array(); foreach ($this->delegate->getPropertyAnnotations($property) as $annot) { $annotations[get_class($annot)] = $annot; } return $annotations; } public function getPropertyAnnotation(\ReflectionProperty $property, $annotation) { return $this->delegate->getPropertyAnnotation($property, $annotation); } public function __call($method, $args) { return call_user_func_array(array($this->delegate, $method), $args); } } parser = new DocParser(); $this->parser->setIgnoreNotImportedAnnotations(true); } public function addNamespace($namespace) { $this->parser->addNamespace($namespace); } public function getClassAnnotations(\ReflectionClass $class) { return $this->parser->parse($class->getDocComment(), 'class '.$class->getName()); } public function getMethodAnnotations(\ReflectionMethod $method) { return $this->parser->parse($method->getDocComment(), 'method '.$method->getDeclaringClass()->name.'::'.$method->getName().'()'); } public function getPropertyAnnotations(\ReflectionProperty $property) { return $this->parser->parse($property->getDocComment(), 'property '.$property->getDeclaringClass()->name.'::$'.$property->getName()); } public function getClassAnnotation(\ReflectionClass $class, $annotationName) { foreach ($this->getClassAnnotations($class) as $annot) { if ($annot instanceof $annotationName) { return $annot; } } return null; } public function getMethodAnnotation(\ReflectionMethod $method, $annotationName) { foreach ($this->getMethodAnnotations($method) as $annot) { if ($annot instanceof $annotationName) { return $annot; } } return null; } public function getPropertyAnnotation(\ReflectionProperty $property, $annotationName) { foreach ($this->getPropertyAnnotations($property) as $annot) { if ($annot instanceof $annotationName) { return $annot; } } return null; } } array( 'is_annotation' => true, 'has_constructor' => true, 'properties' => array(), 'targets_literal' => 'ANNOTATION_CLASS', 'targets' => Target::TARGET_CLASS, 'default_property' => 'value', 'attribute_types' => array( 'value' => array( 'required' => false, 'type' =>'array', 'array_type'=>'string', 'value' =>'array' ) ), ), 'Doctrine\Common\Annotations\Annotation\Attribute' => array( 'is_annotation' => true, 'has_constructor' => false, 'targets_literal' => 'ANNOTATION_ANNOTATION', 'targets' => Target::TARGET_ANNOTATION, 'default_property' => 'name', 'properties' => array( 'name' => 'name', 'type' => 'type', 'required' => 'required' ), 'attribute_types' => array( 'value' => array( 'required' => true, 'type' =>'string', 'value' =>'string' ), 'type' => array( 'required' =>true, 'type' =>'string', 'value' =>'string' ), 'required' => array( 'required' =>false, 'type' =>'boolean', 'value' =>'boolean' ) ), ), 'Doctrine\Common\Annotations\Annotation\Attributes' => array( 'is_annotation' => true, 'has_constructor' => false, 'targets_literal' => 'ANNOTATION_CLASS', 'targets' => Target::TARGET_CLASS, 'default_property' => 'value', 'properties' => array( 'value' => 'value' ), 'attribute_types' => array( 'value' => array( 'type' =>'array', 'required' =>true, 'array_type'=>'Doctrine\Common\Annotations\Annotation\Attribute', 'value' =>'array' ) ), ), 'Doctrine\Common\Annotations\Annotation\Enum' => array( 'is_annotation' => true, 'has_constructor' => true, 'targets_literal' => 'ANNOTATION_PROPERTY', 'targets' => Target::TARGET_PROPERTY, 'default_property' => 'value', 'properties' => array( 'value' => 'value' ), 'attribute_types' => array( 'value' => array( 'type' => 'array', 'required' => true, ), 'literal' => array( 'type' => 'array', 'required' => false, ), ), ), ); private static $typeMap = array( 'float' => 'double', 'bool' => 'boolean', 'Boolean' => 'boolean', 'int' => 'integer', ); public function __construct() { $this->lexer = new DocLexer; } public function setIgnoredAnnotationNames(array $names) { $this->ignoredAnnotationNames = $names; } public function setIgnoredAnnotationNamespaces($ignoredAnnotationNamespaces) { $this->ignoredAnnotationNamespaces = $ignoredAnnotationNamespaces; } public function setIgnoreNotImportedAnnotations($bool) { $this->ignoreNotImportedAnnotations = (boolean) $bool; } public function addNamespace($namespace) { if ($this->imports) { throw new \RuntimeException('You must either use addNamespace(), or setImports(), but not both.'); } $this->namespaces[] = $namespace; } public function setImports(array $imports) { if ($this->namespaces) { throw new \RuntimeException('You must either use addNamespace(), or setImports(), but not both.'); } $this->imports = $imports; } public function setTarget($target) { $this->target = $target; } public function parse($input, $context = '') { $pos = $this->findInitialTokenPosition($input); if ($pos === null) { return array(); } $this->context = $context; $this->lexer->setInput(trim(substr($input, $pos), '* /')); $this->lexer->moveNext(); return $this->Annotations(); } private function findInitialTokenPosition($input) { $pos = 0; while (($pos = strpos($input, '@', $pos)) !== false) { $preceding = substr($input, $pos - 1, 1); if ($pos === 0 || $preceding === ' ' || $preceding === '*' || $preceding === "\t") { return $pos; } $pos++; } return null; } private function match($token) { if ( ! $this->lexer->isNextToken($token) ) { $this->syntaxError($this->lexer->getLiteral($token)); } return $this->lexer->moveNext(); } private function matchAny(array $tokens) { if ( ! $this->lexer->isNextTokenAny($tokens)) { $this->syntaxError(implode(' or ', array_map(array($this->lexer, 'getLiteral'), $tokens))); } return $this->lexer->moveNext(); } private function syntaxError($expected, $token = null) { if ($token === null) { $token = $this->lexer->lookahead; } $message = sprintf('Expected %s, got ', $expected); $message .= ($this->lexer->lookahead === null) ? 'end of string' : sprintf("'%s' at position %s", $token['value'], $token['position']); if (strlen($this->context)) { $message .= ' in ' . $this->context; } $message .= '.'; throw AnnotationException::syntaxError($message); } private function classExists($fqcn) { if (isset($this->classExists[$fqcn])) { return $this->classExists[$fqcn]; } if (class_exists($fqcn, false)) { return $this->classExists[$fqcn] = true; } return $this->classExists[$fqcn] = AnnotationRegistry::loadAnnotationClass($fqcn); } private function collectAnnotationMetadata($name) { if (self::$metadataParser === null) { self::$metadataParser = new self(); self::$metadataParser->setIgnoreNotImportedAnnotations(true); self::$metadataParser->setIgnoredAnnotationNames($this->ignoredAnnotationNames); self::$metadataParser->setImports(array( 'enum' => 'Doctrine\Common\Annotations\Annotation\Enum', 'target' => 'Doctrine\Common\Annotations\Annotation\Target', 'attribute' => 'Doctrine\Common\Annotations\Annotation\Attribute', 'attributes' => 'Doctrine\Common\Annotations\Annotation\Attributes' )); AnnotationRegistry::registerFile(__DIR__ . '/Annotation/Enum.php'); AnnotationRegistry::registerFile(__DIR__ . '/Annotation/Target.php'); AnnotationRegistry::registerFile(__DIR__ . '/Annotation/Attribute.php'); AnnotationRegistry::registerFile(__DIR__ . '/Annotation/Attributes.php'); } $class = new \ReflectionClass($name); $docComment = $class->getDocComment(); $metadata = array( 'default_property' => null, 'has_constructor' => (null !== $constructor = $class->getConstructor()) && $constructor->getNumberOfParameters() > 0, 'properties' => array(), 'property_types' => array(), 'attribute_types' => array(), 'targets_literal' => null, 'targets' => Target::TARGET_ALL, 'is_annotation' => false !== strpos($docComment, '@Annotation'), ); if ($metadata['is_annotation']) { self::$metadataParser->setTarget(Target::TARGET_CLASS); foreach (self::$metadataParser->parse($docComment, 'class @' . $name) as $annotation) { if ($annotation instanceof Target) { $metadata['targets'] = $annotation->targets; $metadata['targets_literal'] = $annotation->literal; continue; } if ($annotation instanceof Attributes) { foreach ($annotation->value as $attribute) { $this->collectAttributeTypeMetadata($metadata, $attribute); } } } if (false === $metadata['has_constructor']) { foreach ($class->getProperties(\ReflectionProperty::IS_PUBLIC) as $property) { $metadata['properties'][$property->name] = $property->name; if (false === ($propertyComment = $property->getDocComment())) { continue; } $attribute = new Attribute(); $attribute->required = (false !== strpos($propertyComment, '@Required')); $attribute->name = $property->name; $attribute->type = (false !== strpos($propertyComment, '@var') && preg_match('/@var\s+([^\s]+)/',$propertyComment, $matches)) ? $matches[1] : 'mixed'; $this->collectAttributeTypeMetadata($metadata, $attribute); if (false !== strpos($propertyComment, '@Enum')) { $context = 'property ' . $class->name . "::\$" . $property->name; self::$metadataParser->setTarget(Target::TARGET_PROPERTY); foreach (self::$metadataParser->parse($propertyComment, $context) as $annotation) { if ( ! $annotation instanceof Enum) { continue; } $metadata['enum'][$property->name]['value'] = $annotation->value; $metadata['enum'][$property->name]['literal'] = ( ! empty($annotation->literal)) ? $annotation->literal : $annotation->value; } } } $metadata['default_property'] = reset($metadata['properties']); } } self::$annotationMetadata[$name] = $metadata; } private function collectAttributeTypeMetadata(&$metadata, Attribute $attribute) { $type = isset(self::$typeMap[$attribute->type]) ? self::$typeMap[$attribute->type] : $attribute->type; if ('mixed' === $type) { return; } switch (true) { case (false !== $pos = strpos($type, '<')): $arrayType = substr($type, $pos + 1, -1); $type = 'array'; if (isset(self::$typeMap[$arrayType])) { $arrayType = self::$typeMap[$arrayType]; } $metadata['attribute_types'][$attribute->name]['array_type'] = $arrayType; break; case (false !== $pos = strrpos($type, '[')): $arrayType = substr($type, 0, $pos); $type = 'array'; if (isset(self::$typeMap[$arrayType])) { $arrayType = self::$typeMap[$arrayType]; } $metadata['attribute_types'][$attribute->name]['array_type'] = $arrayType; break; } $metadata['attribute_types'][$attribute->name]['type'] = $type; $metadata['attribute_types'][$attribute->name]['value'] = $attribute->type; $metadata['attribute_types'][$attribute->name]['required'] = $attribute->required; } private function Annotations() { $annotations = array(); while (null !== $this->lexer->lookahead) { if (DocLexer::T_AT !== $this->lexer->lookahead['type']) { $this->lexer->moveNext(); continue; } if (null !== $this->lexer->token && $this->lexer->lookahead['position'] === $this->lexer->token['position'] + strlen($this->lexer->token['value'])) { $this->lexer->moveNext(); continue; } if ((null === $peek = $this->lexer->glimpse()) || (DocLexer::T_NAMESPACE_SEPARATOR !== $peek['type'] && !in_array($peek['type'], self::$classIdentifiers, true)) || $peek['position'] !== $this->lexer->lookahead['position'] + 1) { $this->lexer->moveNext(); continue; } $this->isNestedAnnotation = false; if (false !== $annot = $this->Annotation()) { $annotations[] = $annot; } } return $annotations; } /** * Annotation ::= "@" AnnotationName MethodCall * AnnotationName ::= QualifiedName | SimpleName * QualifiedName ::= NameSpacePart "\" {NameSpacePart "\"}* SimpleName * NameSpacePart ::= identifier | null | false | true * SimpleName ::= identifier | null | false | true * * @return mixed False if it is not a valid annotation. * * @throws AnnotationException */ private function Annotation() { $this->match(DocLexer::T_AT); $name = $this->Identifier(); $originalName = $name; if ('\\' !== $name[0]) { $pos = strpos($name, '\\'); $alias = (false === $pos)? $name : substr($name, 0, $pos); $found = false; $loweredAlias = strtolower($alias); if ($this->namespaces) { foreach ($this->namespaces as $namespace) { if ($this->classExists($namespace.'\\'.$name)) { $name = $namespace.'\\'.$name; $found = true; break; } } } elseif (isset($this->imports[$loweredAlias])) { $found = true; $name = (false !== $pos) ? $this->imports[$loweredAlias] . substr($name, $pos) : $this->imports[$loweredAlias]; } elseif ( ! isset($this->ignoredAnnotationNames[$name]) && isset($this->imports['__NAMESPACE__']) && $this->classExists($this->imports['__NAMESPACE__'] . '\\' . $name) ) { $name = $this->imports['__NAMESPACE__'].'\\'.$name; $found = true; } elseif (! isset($this->ignoredAnnotationNames[$name]) && $this->classExists($name)) { $found = true; } if ( ! $found) { if ($this->isIgnoredAnnotation($name)) { return false; } throw AnnotationException::semanticalError(sprintf('The annotation "@%s" in %s was never imported. Did you maybe forget to add a "use" statement for this annotation?', $name, $this->context)); } } $name = ltrim($name,'\\'); if ( ! $this->classExists($name)) { throw AnnotationException::semanticalError(sprintf('The annotation "@%s" in %s does not exist, or could not be auto-loaded.', $name, $this->context)); } if ( ! isset(self::$annotationMetadata[$name])) { $this->collectAnnotationMetadata($name); } if (self::$annotationMetadata[$name]['is_annotation'] === false) { if ($this->ignoreNotImportedAnnotations || isset($this->ignoredAnnotationNames[$originalName])) { return false; } throw AnnotationException::semanticalError(sprintf('The class "%s" is not annotated with @Annotation. Are you sure this class can be used as annotation? If so, then you need to add @Annotation to the _class_ doc comment of "%s". If it is indeed no annotation, then you need to add @IgnoreAnnotation("%s") to the _class_ doc comment of %s.', $name, $name, $originalName, $this->context)); } $target = $this->isNestedAnnotation ? Target::TARGET_ANNOTATION : $this->target; $this->isNestedAnnotation = true; if (0 === (self::$annotationMetadata[$name]['targets'] & $target) && $target) { throw AnnotationException::semanticalError( sprintf('Annotation @%s is not allowed to be declared on %s. You may only use this annotation on these code elements: %s.', $originalName, $this->context, self::$annotationMetadata[$name]['targets_literal']) ); } $values = $this->MethodCall(); if (isset(self::$annotationMetadata[$name]['enum'])) { foreach (self::$annotationMetadata[$name]['enum'] as $property => $enum) { if (isset($values[$property]) && ! in_array($values[$property], $enum['value'])) { throw AnnotationException::enumeratorError($property, $name, $this->context, $enum['literal'], $values[$property]); } } } foreach (self::$annotationMetadata[$name]['attribute_types'] as $property => $type) { if ($property === self::$annotationMetadata[$name]['default_property'] && !isset($values[$property]) && isset($values['value'])) { $property = 'value'; } if (!isset($values[$property])) { if ($type['required']) { throw AnnotationException::requiredError($property, $originalName, $this->context, 'a(n) '.$type['value']); } continue; } if ($type['type'] === 'array') { if ( ! is_array($values[$property])) { $values[$property] = array($values[$property]); } if (isset($type['array_type'])) { foreach ($values[$property] as $item) { if (gettype($item) !== $type['array_type'] && !$item instanceof $type['array_type']) { throw AnnotationException::attributeTypeError($property, $originalName, $this->context, 'either a(n) '.$type['array_type'].', or an array of '.$type['array_type'].'s', $item); } } } } elseif (gettype($values[$property]) !== $type['type'] && !$values[$property] instanceof $type['type']) { throw AnnotationException::attributeTypeError($property, $originalName, $this->context, 'a(n) '.$type['value'], $values[$property]); } } if (self::$annotationMetadata[$name]['has_constructor'] === true) { return new $name($values); } $instance = new $name(); foreach ($values as $property => $value) { if (!isset(self::$annotationMetadata[$name]['properties'][$property])) { if ('value' !== $property) { throw AnnotationException::creationError(sprintf('The annotation @%s declared on %s does not have a property named "%s". Available properties: %s', $originalName, $this->context, $property, implode(', ', self::$annotationMetadata[$name]['properties']))); } if ( ! $property = self::$annotationMetadata[$name]['default_property']) { throw AnnotationException::creationError(sprintf('The annotation @%s declared on %s does not accept any values, but got %s.', $originalName, $this->context, json_encode($values))); } } $instance->{$property} = $value; } return $instance; } private function MethodCall() { $values = array(); if ( ! $this->lexer->isNextToken(DocLexer::T_OPEN_PARENTHESIS)) { return $values; } $this->match(DocLexer::T_OPEN_PARENTHESIS); if ( ! $this->lexer->isNextToken(DocLexer::T_CLOSE_PARENTHESIS)) { $values = $this->Values(); } $this->match(DocLexer::T_CLOSE_PARENTHESIS); return $values; } private function Values() { $values = array($this->Value()); while ($this->lexer->isNextToken(DocLexer::T_COMMA)) { $this->match(DocLexer::T_COMMA); if ($this->lexer->isNextToken(DocLexer::T_CLOSE_PARENTHESIS)) { break; } $token = $this->lexer->lookahead; $value = $this->Value(); if ( ! is_object($value) && ! is_array($value)) { $this->syntaxError('Value', $token); } $values[] = $value; } foreach ($values as $k => $value) { if (is_object($value) && $value instanceof \stdClass) { $values[$value->name] = $value->value; } else if ( ! isset($values['value'])){ $values['value'] = $value; } else { if ( ! is_array($values['value'])) { $values['value'] = array($values['value']); } $values['value'][] = $value; } unset($values[$k]); } return $values; } private function Constant() { $identifier = $this->Identifier(); if ( ! defined($identifier) && false !== strpos($identifier, '::') && '\\' !== $identifier[0]) { list($className, $const) = explode('::', $identifier); $pos = strpos($className, '\\'); $alias = (false === $pos) ? $className : substr($className, 0, $pos); $found = false; $loweredAlias = strtolower($alias); switch (true) { case !empty ($this->namespaces): foreach ($this->namespaces as $ns) { if (class_exists($ns.'\\'.$className) || interface_exists($ns.'\\'.$className)) { $className = $ns.'\\'.$className; $found = true; break; } } break; case isset($this->imports[$loweredAlias]): $found = true; $className = (false !== $pos) ? $this->imports[$loweredAlias] . substr($className, $pos) : $this->imports[$loweredAlias]; break; default: if(isset($this->imports['__NAMESPACE__'])) { $ns = $this->imports['__NAMESPACE__']; if (class_exists($ns.'\\'.$className) || interface_exists($ns.'\\'.$className)) { $className = $ns.'\\'.$className; $found = true; } } break; } if ($found) { $identifier = $className . '::' . $const; } } $classPos = stripos($identifier, '::class'); if ($classPos === strlen($identifier) - 7) { return substr($identifier, 0, $classPos); } if (!defined($identifier)) { throw AnnotationException::semanticalErrorConstants($identifier, $this->context); } return constant($identifier); } private function Identifier() { if ( ! $this->lexer->isNextTokenAny(self::$classIdentifiers)) { $this->syntaxError('namespace separator or identifier'); } $this->lexer->moveNext(); $className = $this->lexer->token['value']; while ($this->lexer->lookahead['position'] === ($this->lexer->token['position'] + strlen($this->lexer->token['value'])) && $this->lexer->isNextToken(DocLexer::T_NAMESPACE_SEPARATOR)) { $this->match(DocLexer::T_NAMESPACE_SEPARATOR); $this->matchAny(self::$classIdentifiers); $className .= '\\' . $this->lexer->token['value']; } return $className; } private function Value() { $peek = $this->lexer->glimpse(); if (DocLexer::T_EQUALS === $peek['type']) { return $this->FieldAssignment(); } return $this->PlainValue(); } private function PlainValue() { if ($this->lexer->isNextToken(DocLexer::T_OPEN_CURLY_BRACES)) { return $this->Arrayx(); } if ($this->lexer->isNextToken(DocLexer::T_AT)) { return $this->Annotation(); } if ($this->lexer->isNextToken(DocLexer::T_IDENTIFIER)) { return $this->Constant(); } switch ($this->lexer->lookahead['type']) { case DocLexer::T_STRING: $this->match(DocLexer::T_STRING); return $this->lexer->token['value']; case DocLexer::T_INTEGER: $this->match(DocLexer::T_INTEGER); return (int)$this->lexer->token['value']; case DocLexer::T_FLOAT: $this->match(DocLexer::T_FLOAT); return (float)$this->lexer->token['value']; case DocLexer::T_TRUE: $this->match(DocLexer::T_TRUE); return true; case DocLexer::T_FALSE: $this->match(DocLexer::T_FALSE); return false; case DocLexer::T_NULL: $this->match(DocLexer::T_NULL); return null; default: $this->syntaxError('PlainValue'); } } private function FieldAssignment() { $this->match(DocLexer::T_IDENTIFIER); $fieldName = $this->lexer->token['value']; $this->match(DocLexer::T_EQUALS); $item = new \stdClass(); $item->name = $fieldName; $item->value = $this->PlainValue(); return $item; } private function Arrayx() { $array = $values = array(); $this->match(DocLexer::T_OPEN_CURLY_BRACES); if ($this->lexer->isNextToken(DocLexer::T_CLOSE_CURLY_BRACES)) { $this->match(DocLexer::T_CLOSE_CURLY_BRACES); return $array; } $values[] = $this->ArrayEntry(); while ($this->lexer->isNextToken(DocLexer::T_COMMA)) { $this->match(DocLexer::T_COMMA); if ($this->lexer->isNextToken(DocLexer::T_CLOSE_CURLY_BRACES)) { break; } $values[] = $this->ArrayEntry(); } $this->match(DocLexer::T_CLOSE_CURLY_BRACES); foreach ($values as $value) { list ($key, $val) = $value; if ($key !== null) { $array[$key] = $val; } else { $array[] = $val; } } return $array; } private function ArrayEntry() { $peek = $this->lexer->glimpse(); if (DocLexer::T_EQUALS === $peek['type'] || DocLexer::T_COLON === $peek['type']) { if ($this->lexer->isNextToken(DocLexer::T_IDENTIFIER)) { $key = $this->Constant(); } else { $this->matchAny(array(DocLexer::T_INTEGER, DocLexer::T_STRING)); $key = $this->lexer->token['value']; } $this->matchAny(array(DocLexer::T_EQUALS, DocLexer::T_COLON)); return array($key, $this->PlainValue()); } return array(null, $this->Value()); } private function isIgnoredAnnotation($name) { if ($this->ignoreNotImportedAnnotations || isset($this->ignoredAnnotationNames[$name])) { return true; } foreach (array_keys($this->ignoredAnnotationNamespaces) as $ignoredAnnotationNamespace) { $ignoredAnnotationNamespace = rtrim($ignoredAnnotationNamespace, '\\') . '\\'; if (0 === stripos(rtrim($name, '\\') . '\\', $ignoredAnnotationNamespace)) { return true; } } return false; } } names = $values['value']; } } self::TARGET_ALL, 'CLASS' => self::TARGET_CLASS, 'METHOD' => self::TARGET_METHOD, 'PROPERTY' => self::TARGET_PROPERTY, 'ANNOTATION' => self::TARGET_ANNOTATION, ); public $value; public $targets; public $literal; public function __construct(array $values) { if (!isset($values['value'])){ $values['value'] = null; } if (is_string($values['value'])){ $values['value'] = array($values['value']); } if (!is_array($values['value'])){ throw new \InvalidArgumentException( sprintf('@Target expects either a string value, or an array of strings, "%s" given.', is_object($values['value']) ? get_class($values['value']) : gettype($values['value']) ) ); } $bitmask = 0; foreach ($values['value'] as $literal) { if(!isset(self::$map[$literal])){ throw new \InvalidArgumentException( sprintf('Invalid Target "%s". Available targets: [%s]', $literal, implode(', ', array_keys(self::$map))) ); } $bitmask |= self::$map[$literal]; } $this->targets = $bitmask; $this->value = $values['value']; $this->literal = implode(', ', $this->value); } } $var) { if( ! in_array($key, $values['value'])) { throw new \InvalidArgumentException(sprintf( 'Undefined enumerator value "%s" for literal "%s".', $key , $var )); } } $this->value = $values['value']; $this->literal = $values['literal']; } } 'Doctrine\Common\Annotations\Annotation\IgnoreAnnotation', ); private static $globalIgnoredNames = array( 'Annotation' => true, 'Attribute' => true, 'Attributes' => true, 'Required' => true, 'Target' => true, 'fix' => true , 'fixme' => true, 'override' => true, 'abstract'=> true, 'access'=> true, 'code' => true, 'deprec'=> true, 'endcode' => true, 'exception'=> true, 'final'=> true, 'ingroup' => true, 'inheritdoc'=> true, 'inheritDoc'=> true, 'magic' => true, 'name'=> true, 'toc' => true, 'tutorial'=> true, 'private' => true, 'static'=> true, 'staticvar'=> true, 'staticVar'=> true, 'throw' => true, 'api' => true, 'author'=> true, 'category'=> true, 'copyright'=> true, 'deprecated'=> true, 'example'=> true, 'filesource'=> true, 'global'=> true, 'ignore'=> true, 'internal'=> true, 'license'=> true, 'link'=> true, 'method' => true, 'package'=> true, 'param'=> true, 'property' => true, 'property-read' => true, 'property-write' => true, 'return'=> true, 'see'=> true, 'since'=> true, 'source' => true, 'subpackage'=> true, 'throws'=> true, 'todo'=> true, 'TODO'=> true, 'usedby'=> true, 'uses' => true, 'var'=> true, 'version'=> true, 'codeCoverageIgnore' => true, 'codeCoverageIgnoreStart' => true, 'codeCoverageIgnoreEnd' => true, 'SuppressWarnings' => true, 'noinspection' => true, 'package_version' => true, 'startuml' => true, 'enduml' => true, ); private static $globalIgnoredNamespaces = array(); static public function addGlobalIgnoredName($name) { self::$globalIgnoredNames[$name] = true; } static public function addGlobalIgnoredNamespace($namespace) { self::$globalIgnoredNamespaces[$namespace] = true; } private $parser; private $preParser; private $phpParser; private $imports = array(); private $ignoredAnnotationNames = array(); public function __construct(DocParser $parser = null) { if (extension_loaded('Zend Optimizer+') && (ini_get('zend_optimizerplus.save_comments') === "0" || ini_get('opcache.save_comments') === "0")) { throw AnnotationException::optimizerPlusSaveComments(); } if (extension_loaded('Zend OPcache') && ini_get('opcache.save_comments') == 0) { throw AnnotationException::optimizerPlusSaveComments(); } if (PHP_VERSION_ID < 70000) { if (extension_loaded('Zend Optimizer+') && (ini_get('zend_optimizerplus.load_comments') === "0" || ini_get('opcache.load_comments') === "0")) { throw AnnotationException::optimizerPlusLoadComments(); } if (extension_loaded('Zend OPcache') && ini_get('opcache.load_comments') == 0) { throw AnnotationException::optimizerPlusLoadComments(); } } AnnotationRegistry::registerFile(__DIR__ . '/Annotation/IgnoreAnnotation.php'); $this->parser = $parser ?: new DocParser(); $this->preParser = new DocParser; $this->preParser->setImports(self::$globalImports); $this->preParser->setIgnoreNotImportedAnnotations(true); $this->phpParser = new PhpParser; } public function getClassAnnotations(ReflectionClass $class) { $this->parser->setTarget(Target::TARGET_CLASS); $this->parser->setImports($this->getClassImports($class)); $this->parser->setIgnoredAnnotationNames($this->getIgnoredAnnotationNames($class)); $this->parser->setIgnoredAnnotationNamespaces(self::$globalIgnoredNamespaces); return $this->parser->parse($class->getDocComment(), 'class ' . $class->getName()); } public function getClassAnnotation(ReflectionClass $class, $annotationName) { $annotations = $this->getClassAnnotations($class); foreach ($annotations as $annotation) { if ($annotation instanceof $annotationName) { return $annotation; } } return null; } public function getPropertyAnnotations(ReflectionProperty $property) { $class = $property->getDeclaringClass(); $context = 'property ' . $class->getName() . "::\$" . $property->getName(); $this->parser->setTarget(Target::TARGET_PROPERTY); $this->parser->setImports($this->getPropertyImports($property)); $this->parser->setIgnoredAnnotationNames($this->getIgnoredAnnotationNames($class)); $this->parser->setIgnoredAnnotationNamespaces(self::$globalIgnoredNamespaces); return $this->parser->parse($property->getDocComment(), $context); } public function getPropertyAnnotation(ReflectionProperty $property, $annotationName) { $annotations = $this->getPropertyAnnotations($property); foreach ($annotations as $annotation) { if ($annotation instanceof $annotationName) { return $annotation; } } return null; } public function getMethodAnnotations(ReflectionMethod $method) { $class = $method->getDeclaringClass(); $context = 'method ' . $class->getName() . '::' . $method->getName() . '()'; $this->parser->setTarget(Target::TARGET_METHOD); $this->parser->setImports($this->getMethodImports($method)); $this->parser->setIgnoredAnnotationNames($this->getIgnoredAnnotationNames($class)); $this->parser->setIgnoredAnnotationNamespaces(self::$globalIgnoredNamespaces); return $this->parser->parse($method->getDocComment(), $context); } public function getMethodAnnotation(ReflectionMethod $method, $annotationName) { $annotations = $this->getMethodAnnotations($method); foreach ($annotations as $annotation) { if ($annotation instanceof $annotationName) { return $annotation; } } return null; } private function getIgnoredAnnotationNames(ReflectionClass $class) { $name = $class->getName(); if (isset($this->ignoredAnnotationNames[$name])) { return $this->ignoredAnnotationNames[$name]; } $this->collectParsingMetadata($class); return $this->ignoredAnnotationNames[$name]; } private function getClassImports(ReflectionClass $class) { $name = $class->getName(); if (isset($this->imports[$name])) { return $this->imports[$name]; } $this->collectParsingMetadata($class); return $this->imports[$name]; } private function getMethodImports(ReflectionMethod $method) { $class = $method->getDeclaringClass(); $classImports = $this->getClassImports($class); if (!method_exists($class, 'getTraits')) { return $classImports; } $traitImports = array(); foreach ($class->getTraits() as $trait) { if ($trait->hasMethod($method->getName()) && $trait->getFileName() === $method->getFileName() ) { $traitImports = array_merge($traitImports, $this->phpParser->parseClass($trait)); } } return array_merge($classImports, $traitImports); } private function getPropertyImports(ReflectionProperty $property) { $class = $property->getDeclaringClass(); $classImports = $this->getClassImports($class); if (!method_exists($class, 'getTraits')) { return $classImports; } $traitImports = array(); foreach ($class->getTraits() as $trait) { if ($trait->hasProperty($property->getName())) { $traitImports = array_merge($traitImports, $this->phpParser->parseClass($trait)); } } return array_merge($classImports, $traitImports); } private function collectParsingMetadata(ReflectionClass $class) { $ignoredAnnotationNames = self::$globalIgnoredNames; $annotations = $this->preParser->parse($class->getDocComment(), 'class ' . $class->name); foreach ($annotations as $annotation) { if ($annotation instanceof IgnoreAnnotation) { foreach ($annotation->names AS $annot) { $ignoredAnnotationNames[$annot] = true; } } } $name = $class->getName(); $this->imports[$name] = array_merge( self::$globalImports, $this->phpParser->parseClass($class), array('__NAMESPACE__' => $class->getNamespaceName()) ); $this->ignoredAnnotationNames[$name] = $ignoredAnnotationNames; } } self::T_AT, ',' => self::T_COMMA, '(' => self::T_OPEN_PARENTHESIS, ')' => self::T_CLOSE_PARENTHESIS, '{' => self::T_OPEN_CURLY_BRACES, '}' => self::T_CLOSE_CURLY_BRACES, '=' => self::T_EQUALS, ':' => self::T_COLON, '\\' => self::T_NAMESPACE_SEPARATOR ); protected $withCase = array( 'true' => self::T_TRUE, 'false' => self::T_FALSE, 'null' => self::T_NULL ); protected function getCatchablePatterns() { return array( '[a-z_\\\][a-z0-9_\:\\\]*[a-z_][a-z0-9_]*', '(?:[+-]?[0-9]+(?:[\.][0-9]+)*)(?:[eE][+-]?[0-9]+)?', '"(?:""|[^"])*+"', ); } protected function getNonCatchablePatterns() { return array('\s+', '\*+', '(.)'); } protected function getType(&$value) { $type = self::T_NONE; if ($value[0] === '"') { $value = str_replace('""', '"', substr($value, 1, strlen($value) - 2)); return self::T_STRING; } if (isset($this->noCase[$value])) { return $this->noCase[$value]; } if ($value[0] === '_' || $value[0] === '\\' || ctype_alpha($value[0])) { return self::T_IDENTIFIER; } $lowerValue = strtolower($value); if (isset($this->withCase[$lowerValue])) { return $this->withCase[$lowerValue]; } if (is_numeric($value)) { return (strpos($value, '.') !== false || stripos($value, 'e') !== false) ? self::T_FLOAT : self::T_INTEGER; } return $type; } } tokens = token_get_all($contents); token_get_all("numTokens = count($this->tokens); } public function next($docCommentIsComment = TRUE) { for ($i = $this->pointer; $i < $this->numTokens; $i++) { $this->pointer++; if ($this->tokens[$i][0] === T_WHITESPACE || $this->tokens[$i][0] === T_COMMENT || ($docCommentIsComment && $this->tokens[$i][0] === T_DOC_COMMENT)) { continue; } return $this->tokens[$i]; } return null; } public function parseUseStatement() { $groupRoot = ''; $class = ''; $alias = ''; $statements = array(); $explicitAlias = false; while (($token = $this->next())) { $isNameToken = $token[0] === T_STRING || $token[0] === T_NS_SEPARATOR; if (!$explicitAlias && $isNameToken) { $class .= $token[1]; $alias = $token[1]; } else if ($explicitAlias && $isNameToken) { $alias .= $token[1]; } else if ($token[0] === T_AS) { $explicitAlias = true; $alias = ''; } else if ($token === ',') { $statements[strtolower($alias)] = $groupRoot . $class; $class = ''; $alias = ''; $explicitAlias = false; } else if ($token === ';') { $statements[strtolower($alias)] = $groupRoot . $class; break; } else if ($token === '{' ) { $groupRoot = $class; $class = ''; } else if ($token === '}' ) { continue; } else { break; } } return $statements; } public function parseUseStatements($namespaceName) { $statements = array(); while (($token = $this->next())) { if ($token[0] === T_USE) { $statements = array_merge($statements, $this->parseUseStatement()); continue; } if ($token[0] !== T_NAMESPACE || $this->parseNamespace() != $namespaceName) { continue; } $statements = array(); } return $statements; } public function parseNamespace() { $name = ''; while (($token = $this->next()) && ($token[0] === T_STRING || $token[0] === T_NS_SEPARATOR)) { $name .= $token[1]; } return $name; } public function parseClass() { return $this->parseNamespace(); } } input = $input; $this->tokens = array(); $this->reset(); $this->scan($input); } public function reset() { $this->lookahead = null; $this->token = null; $this->peek = 0; $this->position = 0; } public function resetPeek() { $this->peek = 0; } public function resetPosition($position = 0) { $this->position = $position; } public function getInputUntilPosition($position) { return substr($this->input, 0, $position); } public function isNextToken($token) { return null !== $this->lookahead && $this->lookahead['type'] === $token; } public function isNextTokenAny(array $tokens) { return null !== $this->lookahead && in_array($this->lookahead['type'], $tokens, true); } public function moveNext() { $this->peek = 0; $this->token = $this->lookahead; $this->lookahead = (isset($this->tokens[$this->position])) ? $this->tokens[$this->position++] : null; return $this->lookahead !== null; } public function skipUntil($type) { while ($this->lookahead !== null && $this->lookahead['type'] !== $type) { $this->moveNext(); } } public function isA($value, $token) { return $this->getType($value) === $token; } public function peek() { if (isset($this->tokens[$this->position + $this->peek])) { return $this->tokens[$this->position + $this->peek++]; } else { return null; } } public function glimpse() { $peek = $this->peek(); $this->peek = 0; return $peek; } protected function scan($input) { static $regex; if ( ! isset($regex)) { $regex = sprintf( '/(%s)|%s/%s', implode(')|(', $this->getCatchablePatterns()), implode('|', $this->getNonCatchablePatterns()), $this->getModifiers() ); } $flags = PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_OFFSET_CAPTURE; $matches = preg_split($regex, $input, -1, $flags); if (false === $matches) { $matches = array(array($input, 0)); } foreach ($matches as $match) { $type = $this->getType($match[0]); $this->tokens[] = array( 'value' => $match[0], 'type' => $type, 'position' => $match[1], ); } } public function getLiteral($token) { $className = get_class($this); $reflClass = new \ReflectionClass($className); $constants = $reflClass->getConstants(); foreach ($constants as $name => $value) { if ($value === $token) { return $className . '::' . $name; } } return $token; } protected function getModifiers() { return 'i'; } abstract protected function getCatchablePatterns(); abstract protected function getNonCatchablePatterns(); abstract protected function getType(&$value); } log(LogLevel::EMERGENCY, $message, $context); } public function alert($message, array $context = array()) { $this->log(LogLevel::ALERT, $message, $context); } public function critical($message, array $context = array()) { $this->log(LogLevel::CRITICAL, $message, $context); } public function error($message, array $context = array()) { $this->log(LogLevel::ERROR, $message, $context); } public function warning($message, array $context = array()) { $this->log(LogLevel::WARNING, $message, $context); } public function notice($message, array $context = array()) { $this->log(LogLevel::NOTICE, $message, $context); } public function info($message, array $context = array()) { $this->log(LogLevel::INFO, $message, $context); } public function debug($message, array $context = array()) { $this->log(LogLevel::DEBUG, $message, $context); } } logger = $logger; } } log(LogLevel::EMERGENCY, $message, $context); } public function alert($message, array $context = array()) { $this->log(LogLevel::ALERT, $message, $context); } public function critical($message, array $context = array()) { $this->log(LogLevel::CRITICAL, $message, $context); } public function error($message, array $context = array()) { $this->log(LogLevel::ERROR, $message, $context); } public function warning($message, array $context = array()) { $this->log(LogLevel::WARNING, $message, $context); } public function notice($message, array $context = array()) { $this->log(LogLevel::NOTICE, $message, $context); } public function info($message, array $context = array()) { $this->log(LogLevel::INFO, $message, $context); } public function debug($message, array $context = array()) { $this->log(LogLevel::DEBUG, $message, $context); } abstract public function log($level, $message, array $context = array()); } $messageLen) { return null; } if (0 !== substr_compare($error['message'], $notFoundSuffix, -$notFoundSuffixLen)) { return null; } $prefix = 'Call to undefined function '; $prefixLen = \strlen($prefix); if (0 !== strpos($error['message'], $prefix)) { return null; } $fullyQualifiedFunctionName = substr($error['message'], $prefixLen, -$notFoundSuffixLen); if (false !== $namespaceSeparatorIndex = strrpos($fullyQualifiedFunctionName, '\\')) { $functionName = substr($fullyQualifiedFunctionName, $namespaceSeparatorIndex + 1); $namespacePrefix = substr($fullyQualifiedFunctionName, 0, $namespaceSeparatorIndex); $message = sprintf('Attempted to call function "%s" from namespace "%s".', $functionName, $namespacePrefix); } else { $functionName = $fullyQualifiedFunctionName; $message = sprintf('Attempted to call function "%s" from the global namespace.', $functionName); } $candidates = []; foreach (get_defined_functions() as $type => $definedFunctionNames) { foreach ($definedFunctionNames as $definedFunctionName) { if (false !== $namespaceSeparatorIndex = strrpos($definedFunctionName, '\\')) { $definedFunctionNameBasename = substr($definedFunctionName, $namespaceSeparatorIndex + 1); } else { $definedFunctionNameBasename = $definedFunctionName; } if ($definedFunctionNameBasename === $functionName) { $candidates[] = '\\'.$definedFunctionName; } } } if ($candidates) { sort($candidates); $last = array_pop($candidates).'"?'; if ($candidates) { $candidates = 'e.g. "'.implode('", "', $candidates).'" or "'.$last; } else { $candidates = '"'.$last; } $message .= "\nDid you mean to call ".$candidates; } return new UndefinedFunctionException($message, $exception); } } $messageLen) { return null; } if (0 !== substr_compare($error['message'], $notFoundSuffix, -$notFoundSuffixLen)) { return null; } foreach (['class', 'interface', 'trait'] as $typeName) { $prefix = ucfirst($typeName).' \''; $prefixLen = \strlen($prefix); if (0 !== strpos($error['message'], $prefix)) { continue; } $fullyQualifiedClassName = substr($error['message'], $prefixLen, -$notFoundSuffixLen); if (false !== $namespaceSeparatorIndex = strrpos($fullyQualifiedClassName, '\\')) { $className = substr($fullyQualifiedClassName, $namespaceSeparatorIndex + 1); $namespacePrefix = substr($fullyQualifiedClassName, 0, $namespaceSeparatorIndex); $message = sprintf('Attempted to load %s "%s" from namespace "%s".', $typeName, $className, $namespacePrefix); $tail = ' for another namespace?'; } else { $className = $fullyQualifiedClassName; $message = sprintf('Attempted to load %s "%s" from the global namespace.', $typeName, $className); $tail = '?'; } if ($candidates = $this->getClassCandidates($className)) { $tail = array_pop($candidates).'"?'; if ($candidates) { $tail = ' for e.g. "'.implode('", "', $candidates).'" or "'.$tail; } else { $tail = ' for "'.$tail; } } $message .= "\nDid you forget a \"use\" statement".$tail; return new ClassNotFoundException($message, $exception); } return null; } private function getClassCandidates($class) { if (!\is_array($functions = spl_autoload_functions())) { return []; } $classes = []; foreach ($functions as $function) { if (!\is_array($function)) { continue; } if ($function[0] instanceof DebugClassLoader) { $function = $function[0]->getClassLoader(); if (!\is_array($function)) { continue; } } if ($function[0] instanceof ComposerClassLoader || $function[0] instanceof SymfonyClassLoader) { foreach ($function[0]->getPrefixes() as $prefix => $paths) { foreach ($paths as $path) { $classes = array_merge($classes, $this->findClassInPath($path, $class, $prefix)); } } } if ($function[0] instanceof ComposerClassLoader) { foreach ($function[0]->getPrefixesPsr4() as $prefix => $paths) { foreach ($paths as $path) { $classes = array_merge($classes, $this->findClassInPath($path, $class, $prefix)); } } } } return array_unique($classes); } private function findClassInPath($path, $class, $prefix) { if (!$path = realpath($path.'/'.strtr($prefix, '\\_', '//')) ?: realpath($path.'/'.\dirname(strtr($prefix, '\\_', '//'))) ?: realpath($path)) { return []; } $classes = []; $filename = $class.'.php'; foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS), \RecursiveIteratorIterator::LEAVES_ONLY) as $file) { if ($filename == $file->getFileName() && $class = $this->convertFileToClass($path, $file->getPathName(), $prefix)) { $classes[] = $class; } } return $classes; } private function convertFileToClass($path, $file, $prefix) { $candidates = [ $namespacedClass = str_replace([$path.\DIRECTORY_SEPARATOR, '.php', '/'], ['', '', '\\'], $file), $prefix.$namespacedClass, $prefix.'\\'.$namespacedClass, str_replace('\\', '_', $namespacedClass), str_replace('\\', '_', $prefix.$namespacedClass), str_replace('\\', '_', $prefix.'\\'.$namespacedClass), ]; if ($prefix) { $candidates = array_filter($candidates, function ($candidate) use ($prefix) { return 0 === strpos($candidate, $prefix); }); } foreach ($candidates as $candidate) { if ($this->classExists($candidate)) { return $candidate; } } try { require_once $file; } catch (\Throwable $e) { return null; } foreach ($candidates as $candidate) { if ($this->classExists($candidate)) { return $candidate; } } return null; } private function classExists($class) { return class_exists($class, false) || interface_exists($class, false) || trait_exists($class, false); } } debug = $debug; $this->charset = $charset ?: ini_get('default_charset') ?: 'UTF-8'; $this->fileLinkFormat = $fileLinkFormat; } public static function register($debug = true, $charset = null, $fileLinkFormat = null) { $handler = new static($debug, $charset, $fileLinkFormat); $prev = set_exception_handler([$handler, 'handle']); if (\is_array($prev) && $prev[0] instanceof ErrorHandler) { restore_exception_handler(); $prev[0]->setExceptionHandler([$handler, 'handle']); } return $handler; } public function setHandler(callable $handler = null) { $old = $this->handler; $this->handler = $handler; return $old; } public function setFileLinkFormat($fileLinkFormat) { $old = $this->fileLinkFormat; $this->fileLinkFormat = $fileLinkFormat; return $old; } public function handle(\Exception $exception) { if (null === $this->handler || $exception instanceof OutOfMemoryException) { $this->sendPhpResponse($exception); return; } $caughtLength = $this->caughtLength = 0; ob_start(function ($buffer) { $this->caughtBuffer = $buffer; return ''; }); $this->sendPhpResponse($exception); while (null === $this->caughtBuffer && ob_end_flush()) { } if (isset($this->caughtBuffer[0])) { ob_start(function ($buffer) { if ($this->caughtLength) { $cleanBuffer = substr_replace($buffer, '', 0, $this->caughtLength); if (isset($cleanBuffer[0])) { $buffer = $cleanBuffer; } } return $buffer; }); echo $this->caughtBuffer; $caughtLength = ob_get_length(); } $this->caughtBuffer = null; try { \call_user_func($this->handler, $exception); $this->caughtLength = $caughtLength; } catch (\Exception $e) { if (!$caughtLength) { throw $exception; } } } public function sendPhpResponse($exception) { if (!$exception instanceof FlattenException) { $exception = FlattenException::create($exception); } if (!headers_sent()) { header(sprintf('HTTP/1.0 %s', $exception->getStatusCode())); foreach ($exception->getHeaders() as $name => $value) { header($name.': '.$value, false); } header('Content-Type: text/html; charset='.$this->charset); } echo $this->decorate($this->getContent($exception), $this->getStylesheet($exception)); } public function getHtml($exception) { if (!$exception instanceof FlattenException) { $exception = FlattenException::create($exception); } return $this->decorate($this->getContent($exception), $this->getStylesheet($exception)); } public function getContent(FlattenException $exception) { switch ($exception->getStatusCode()) { case 404: $title = 'Sorry, the page you are looking for could not be found.'; break; default: $title = 'Whoops, looks like something went wrong.'; } if (!$this->debug) { return <<

$title

EOF; } $content = ''; try { $count = \count($exception->getAllPrevious()); $total = $count + 1; foreach ($exception->toArray() as $position => $e) { $ind = $count - $position + 1; $class = $this->formatClass($e['class']); $message = nl2br($this->escapeHtml($e['message'])); $content .= sprintf(<<<'EOF'
EOF , $ind, $total, $class, $message); foreach ($e['trace'] as $trace) { $content .= '\n"; } $content .= "\n

(%d/%d) %s

%s

'; if ($trace['function']) { $content .= sprintf('at %s%s%s(%s)', $this->formatClass($trace['class']), $trace['type'], $trace['function'], $this->formatArgs($trace['args'])); } if (isset($trace['file']) && isset($trace['line'])) { $content .= $this->formatPath($trace['file'], $trace['line']); } $content .= "
\n
\n"; } } catch (\Exception $e) { if ($this->debug) { $title = sprintf('Exception thrown when handling an exception (%s: %s)', \get_class($e), $this->escapeHtml($e->getMessage())); } else { $title = 'Whoops, looks like something went wrong.'; } } $symfonyGhostImageContents = $this->getSymfonyGhostAsSvg(); return <<

$title

$symfonyGhostImageContents
$content
EOF; } public function getStylesheet(FlattenException $exception) { if (!$this->debug) { return <<<'EOF' body { background-color: #fff; color: #222; font: 16px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; margin: 0; } .container { margin: 30px; max-width: 600px; } h1 { color: #dc3545; font-size: 24px; } EOF; } return <<<'EOF' body { background-color: #F9F9F9; color: #222; font: 14px/1.4 Helvetica, Arial, sans-serif; margin: 0; padding-bottom: 45px; } a { cursor: pointer; text-decoration: none; } a:hover { text-decoration: underline; } abbr[title] { border-bottom: none; cursor: help; text-decoration: none; } code, pre { font: 13px/1.5 Consolas, Monaco, Menlo, "Ubuntu Mono", "Liberation Mono", monospace; } table, tr, th, td { background: #FFF; border-collapse: collapse; vertical-align: top; } table { background: #FFF; border: 1px solid #E0E0E0; box-shadow: 0px 0px 1px rgba(128, 128, 128, .2); margin: 1em 0; width: 100%; } table th, table td { border: solid #E0E0E0; border-width: 1px 0; padding: 8px 10px; } table th { background-color: #E0E0E0; font-weight: bold; text-align: left; } .hidden-xs-down { display: none; } .block { display: block; } .break-long-words { -ms-word-break: break-all; word-break: break-all; word-break: break-word; -webkit-hyphens: auto; -moz-hyphens: auto; hyphens: auto; } .text-muted { color: #999; } .container { max-width: 1024px; margin: 0 auto; padding: 0 15px; } .container::after { content: ""; display: table; clear: both; } .exception-summary { background: #B0413E; border-bottom: 2px solid rgba(0, 0, 0, 0.1); border-top: 1px solid rgba(0, 0, 0, .3); flex: 0 0 auto; margin-bottom: 30px; } .exception-message-wrapper { display: flex; align-items: center; min-height: 70px; } .exception-message { flex-grow: 1; padding: 30px 0; } .exception-message, .exception-message a { color: #FFF; font-size: 21px; font-weight: 400; margin: 0; } .exception-message.long { font-size: 18px; } .exception-message a { border-bottom: 1px solid rgba(255, 255, 255, 0.5); font-size: inherit; text-decoration: none; } .exception-message a:hover { border-bottom-color: #ffffff; } .exception-illustration { flex-basis: 111px; flex-shrink: 0; height: 66px; margin-left: 15px; opacity: .7; } .trace + .trace { margin-top: 30px; } .trace-head .trace-class { color: #222; font-size: 18px; font-weight: bold; line-height: 1.3; margin: 0; position: relative; } .trace-message { font-size: 14px; font-weight: normal; margin: .5em 0 0; } .trace-file-path, .trace-file-path a { color: #222; margin-top: 3px; font-size: 13px; } .trace-class { color: #B0413E; } .trace-type { padding: 0 2px; } .trace-method { color: #B0413E; font-weight: bold; } .trace-arguments { color: #777; font-weight: normal; padding-left: 2px; } @media (min-width: 575px) { .hidden-xs-down { display: initial; } } EOF; } private function decorate($content, $css) { return << $content EOF; } private function formatClass($class) { $parts = explode('\\', $class); return sprintf('%s', $class, array_pop($parts)); } private function formatPath($path, $line) { $file = $this->escapeHtml(preg_match('#[^/\\\\]*+$#', $path, $file) ? $file[0] : $path); $fmt = $this->fileLinkFormat ?: ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format'); if (!$fmt) { return sprintf('in %s%s', $this->escapeHtml($path), $file, 0 < $line ? ' line '.$line : ''); } if (\is_string($fmt)) { $i = strpos($f = $fmt, '&', max(strrpos($f, '%f'), strrpos($f, '%l'))) ?: \strlen($f); $fmt = [substr($f, 0, $i)] + preg_split('/&([^>]++)>/', substr($f, $i), -1, PREG_SPLIT_DELIM_CAPTURE); for ($i = 1; isset($fmt[$i]); ++$i) { if (0 === strpos($path, $k = $fmt[$i++])) { $path = substr_replace($path, $fmt[$i], 0, \strlen($k)); break; } } $link = strtr($fmt[0], ['%f' => $path, '%l' => $line]); } else { try { $link = $fmt->format($path, $line); } catch (\Exception $e) { return sprintf('in %s%s', $this->escapeHtml($path), $file, 0 < $line ? ' line '.$line : ''); } } return sprintf('in %s
%s', $this->escapeHtml($link), $file, 0 < $line ? ' line '.$line : ''); } private function formatArgs(array $args) { $result = []; foreach ($args as $key => $item) { if ('object' === $item[0]) { $formattedValue = sprintf('object(%s)', $this->formatClass($item[1])); } elseif ('array' === $item[0]) { $formattedValue = sprintf('array(%s)', \is_array($item[1]) ? $this->formatArgs($item[1]) : $item[1]); } elseif ('null' === $item[0]) { $formattedValue = 'null'; } elseif ('boolean' === $item[0]) { $formattedValue = ''.strtolower(var_export($item[1], true)).''; } elseif ('resource' === $item[0]) { $formattedValue = 'resource'; } else { $formattedValue = str_replace("\n", '', $this->escapeHtml(var_export($item[1], true))); } $result[] = \is_int($key) ? $formattedValue : sprintf("'%s' => %s", $this->escapeHtml($key), $formattedValue); } return implode(', ', $result); } private function escapeHtml($str) { return htmlspecialchars($str, ENT_COMPAT | ENT_SUBSTITUTE, $this->charset); } private function getSymfonyGhostAsSvg() { return ''; } } 1, 'float' => 1, 'bool' => 1, 'string' => 1, 'true' => 1, 'false' => 1, 'null' => 1]; private static $darwinCache = ['/' => ['/', []]]; public function __construct(callable $classLoader) { $this->classLoader = $classLoader; $this->isFinder = \is_array($classLoader) && method_exists($classLoader[0], 'findFile'); if (!isset(self::$caseCheck)) { $file = file_exists(__FILE__) ? __FILE__ : rtrim(realpath('.'), \DIRECTORY_SEPARATOR); $i = strrpos($file, \DIRECTORY_SEPARATOR); $dir = substr($file, 0, 1 + $i); $file = substr($file, 1 + $i); $test = strtoupper($file) === $file ? strtolower($file) : strtoupper($file); $test = realpath($dir.$test); if (false === $test || false === $i) { self::$caseCheck = 0; } elseif (substr($test, -\strlen($file)) === $file) { self::$caseCheck = 1; } elseif (false !== stripos(PHP_OS, 'darwin')) { self::$caseCheck = 2; } else { self::$caseCheck = 0; } } } public function getClassLoader() { return $this->classLoader; } public static function enable() { class_exists('Symfony\Component\Debug\ErrorHandler'); class_exists('Psr\Log\LogLevel'); if (!\is_array($functions = spl_autoload_functions())) { return; } foreach ($functions as $function) { spl_autoload_unregister($function); } foreach ($functions as $function) { if (!\is_array($function) || !$function[0] instanceof self) { $function = [new static($function), 'loadClass']; } spl_autoload_register($function); } } public static function disable() { if (!\is_array($functions = spl_autoload_functions())) { return; } foreach ($functions as $function) { spl_autoload_unregister($function); } foreach ($functions as $function) { if (\is_array($function) && $function[0] instanceof self) { $function = $function[0]->getClassLoader(); } spl_autoload_register($function); } } public function findFile($class) { return $this->isFinder ? $this->classLoader[0]->findFile($class) ?: null : null; } public function loadClass($class) { $e = error_reporting(error_reporting() | E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR); try { if ($this->isFinder && !isset($this->loaded[$class])) { $this->loaded[$class] = true; if (!$file = $this->classLoader[0]->findFile($class) ?: false) { } elseif (\function_exists('opcache_is_script_cached') && @opcache_is_script_cached($file)) { include $file; return; } elseif (false === include $file) { return; } } else { \call_user_func($this->classLoader, $class); $file = false; } } finally { error_reporting($e); } $this->checkClass($class, $file); } private function checkClass($class, $file = null) { $exists = null === $file || class_exists($class, false) || interface_exists($class, false) || trait_exists($class, false); if (null !== $file && $class && '\\' === $class[0]) { $class = substr($class, 1); } if ($exists) { if (isset(self::$checkedClasses[$class])) { return; } self::$checkedClasses[$class] = true; $refl = new \ReflectionClass($class); if (null === $file && $refl->isInternal()) { return; } $name = $refl->getName(); if ($name !== $class && 0 === strcasecmp($name, $class)) { throw new \RuntimeException(sprintf('Case mismatch between loaded and declared class names: "%s" vs "%s".', $class, $name)); } $deprecations = $this->checkAnnotations($refl, $name); if (isset(self::$php7Reserved[strtolower($refl->getShortName())])) { $deprecations[] = sprintf('The "%s" class uses the reserved name "%s", it will break on PHP 7 and higher', $name, $refl->getShortName()); } foreach ($deprecations as $message) { @trigger_error($message, E_USER_DEPRECATED); } } if (!$file) { return; } if (!$exists) { if (false !== strpos($class, '/')) { throw new \RuntimeException(sprintf('Trying to autoload a class with an invalid name "%s". Be careful that the namespace separator is "\" in PHP, not "/".', $class)); } throw new \RuntimeException(sprintf('The autoloader expected class "%s" to be defined in file "%s". The file was found but the class was not in it, the class name or namespace probably has a typo.', $class, $file)); } if (self::$caseCheck && $message = $this->checkCase($refl, $file, $class)) { throw new \RuntimeException(sprintf('Case mismatch between class and real file names: "%s" vs "%s" in "%s".', $message[0], $message[1], $message[2])); } } public function checkAnnotations(\ReflectionClass $refl, $class) { $deprecations = []; if (2 > $len = 1 + (strpos($class, '\\') ?: strpos($class, '_'))) { $len = 0; $ns = ''; } else { $ns = str_replace('_', '\\', substr($class, 0, $len)); } if (false !== $doc = $refl->getDocComment()) { foreach (['final', 'deprecated', 'internal'] as $annotation) { if (false !== strpos($doc, $annotation) && preg_match('#\n\s+\* @'.$annotation.'(?:( .+?)\.?)?\r?\n\s+\*(?: @|/$|\r?\n)#s', $doc, $notice)) { self::${$annotation}[$class] = isset($notice[1]) ? preg_replace('#\.?\r?\n( \*)? *(?= |\r?\n|$)#', '', $notice[1]) : ''; } } } $parent = get_parent_class($class); $parentAndOwnInterfaces = $this->getOwnInterfaces($class, $parent); if ($parent) { $parentAndOwnInterfaces[$parent] = $parent; if (!isset(self::$checkedClasses[$parent])) { $this->checkClass($parent); } if (isset(self::$final[$parent])) { $deprecations[] = sprintf('The "%s" class is considered final%s. It may change without further notice as of its next major version. You should not extend it from "%s".', $parent, self::$final[$parent], $class); } } foreach ($parentAndOwnInterfaces + class_uses($class, false) as $use) { if (!isset(self::$checkedClasses[$use])) { $this->checkClass($use); } if (isset(self::$deprecated[$use]) && strncmp($ns, str_replace('_', '\\', $use), $len) && !isset(self::$deprecated[$class])) { $type = class_exists($class, false) ? 'class' : (interface_exists($class, false) ? 'interface' : 'trait'); $verb = class_exists($use, false) || interface_exists($class, false) ? 'extends' : (interface_exists($use, false) ? 'implements' : 'uses'); $deprecations[] = sprintf('The "%s" %s %s "%s" that is deprecated%s.', $class, $type, $verb, $use, self::$deprecated[$use]); } if (isset(self::$internal[$use]) && strncmp($ns, str_replace('_', '\\', $use), $len)) { $deprecations[] = sprintf('The "%s" %s is considered internal%s. It may change without further notice. You should not use it from "%s".', $use, class_exists($use, false) ? 'class' : (interface_exists($use, false) ? 'interface' : 'trait'), self::$internal[$use], $class); } } if (trait_exists($class)) { return $deprecations; } self::$finalMethods[$class] = []; self::$internalMethods[$class] = []; foreach ($parentAndOwnInterfaces as $use) { foreach (['finalMethods', 'internalMethods'] as $property) { if (isset(self::${$property}[$use])) { self::${$property}[$class] = self::${$property}[$class] ? self::${$property}[$use] + self::${$property}[$class] : self::${$property}[$use]; } } } foreach ($refl->getMethods(\ReflectionMethod::IS_PUBLIC | \ReflectionMethod::IS_PROTECTED) as $method) { if ($method->class !== $class) { continue; } if ($parent && isset(self::$finalMethods[$parent][$method->name])) { list($declaringClass, $message) = self::$finalMethods[$parent][$method->name]; $deprecations[] = sprintf('The "%s::%s()" method is considered final%s. It may change without further notice as of its next major version. You should not extend it from "%s".', $declaringClass, $method->name, $message, $class); } if (isset(self::$internalMethods[$class][$method->name])) { list($declaringClass, $message) = self::$internalMethods[$class][$method->name]; if (strncmp($ns, $declaringClass, $len)) { $deprecations[] = sprintf('The "%s::%s()" method is considered internal%s. It may change without further notice. You should not extend it from "%s".', $declaringClass, $method->name, $message, $class); } } if (false === $doc = $method->getDocComment()) { continue; } foreach (['final', 'internal'] as $annotation) { if (false !== strpos($doc, $annotation) && preg_match('#\n\s+\* @'.$annotation.'(?:( .+?)\.?)?\r?\n\s+\*(?: @|/$|\r?\n)#s', $doc, $notice)) { $message = isset($notice[1]) ? preg_replace('#\.?\r?\n( \*)? *(?= |\r?\n|$)#', '', $notice[1]) : ''; self::${$annotation.'Methods'}[$class][$method->name] = [$class, $message]; } } } return $deprecations; } public function checkCase(\ReflectionClass $refl, $file, $class) { $real = explode('\\', $class.strrchr($file, '.')); $tail = explode(\DIRECTORY_SEPARATOR, str_replace('/', \DIRECTORY_SEPARATOR, $file)); $i = \count($tail) - 1; $j = \count($real) - 1; while (isset($tail[$i], $real[$j]) && $tail[$i] === $real[$j]) { --$i; --$j; } array_splice($tail, 0, $i + 1); if (!$tail) { return null; } $tail = \DIRECTORY_SEPARATOR.implode(\DIRECTORY_SEPARATOR, $tail); $tailLen = \strlen($tail); $real = $refl->getFileName(); if (2 === self::$caseCheck) { $real = $this->darwinRealpath($real); } if (0 === substr_compare($real, $tail, -$tailLen, $tailLen, true) && 0 !== substr_compare($real, $tail, -$tailLen, $tailLen, false) ) { return [substr($tail, -$tailLen + 1), substr($real, -$tailLen + 1), substr($real, 0, -$tailLen + 1)]; } return null; } private function darwinRealpath($real) { $i = 1 + strrpos($real, '/'); $file = substr($real, $i); $real = substr($real, 0, $i); if (isset(self::$darwinCache[$real])) { $kDir = $real; } else { $kDir = strtolower($real); if (isset(self::$darwinCache[$kDir])) { $real = self::$darwinCache[$kDir][0]; } else { $dir = getcwd(); chdir($real); $real = getcwd().'/'; chdir($dir); $dir = $real; $k = $kDir; $i = \strlen($dir) - 1; while (!isset(self::$darwinCache[$k])) { self::$darwinCache[$k] = [$dir, []]; self::$darwinCache[$dir] = &self::$darwinCache[$k]; while ('/' !== $dir[--$i]) { } $k = substr($k, 0, ++$i); $dir = substr($dir, 0, $i--); } } } $dirFiles = self::$darwinCache[$kDir][1]; if (!isset($dirFiles[$file]) && ') : eval()\'d code' === substr($file, -17)) { $file = substr($file, 0, strrpos($file, '(', -17)); } if (isset($dirFiles[$file])) { return $real.$dirFiles[$file]; } $kFile = strtolower($file); if (!isset($dirFiles[$kFile])) { foreach (scandir($real, 2) as $f) { if ('.' !== $f[0]) { $dirFiles[$f] = $f; if ($f === $file) { $kFile = $k = $file; } elseif ($f !== $k = strtolower($f)) { $dirFiles[$k] = $f; } } } self::$darwinCache[$kDir][1] = $dirFiles; } return $real.$dirFiles[$kFile]; } private function getOwnInterfaces($class, $parent) { $ownInterfaces = class_implements($class, false); if ($parent) { foreach (class_implements($parent, false) as $interface) { unset($ownInterfaces[$interface]); } } foreach ($ownInterfaces as $interface) { foreach (class_implements($interface) as $interface) { unset($ownInterfaces[$interface]); } } return $ownInterfaces; } } * @author Grégoire Pineau */ class ErrorHandler { private $levels = [ E_DEPRECATED => 'Deprecated', E_USER_DEPRECATED => 'User Deprecated', E_NOTICE => 'Notice', E_USER_NOTICE => 'User Notice', E_STRICT => 'Runtime Notice', E_WARNING => 'Warning', E_USER_WARNING => 'User Warning', E_COMPILE_WARNING => 'Compile Warning', E_CORE_WARNING => 'Core Warning', E_USER_ERROR => 'User Error', E_RECOVERABLE_ERROR => 'Catchable Fatal Error', E_COMPILE_ERROR => 'Compile Error', E_PARSE => 'Parse Error', E_ERROR => 'Error', E_CORE_ERROR => 'Core Error', ]; private $loggers = [ E_DEPRECATED => [null, LogLevel::INFO], E_USER_DEPRECATED => [null, LogLevel::INFO], E_NOTICE => [null, LogLevel::WARNING], E_USER_NOTICE => [null, LogLevel::WARNING], E_STRICT => [null, LogLevel::WARNING], E_WARNING => [null, LogLevel::WARNING], E_USER_WARNING => [null, LogLevel::WARNING], E_COMPILE_WARNING => [null, LogLevel::WARNING], E_CORE_WARNING => [null, LogLevel::WARNING], E_USER_ERROR => [null, LogLevel::CRITICAL], E_RECOVERABLE_ERROR => [null, LogLevel::CRITICAL], E_COMPILE_ERROR => [null, LogLevel::CRITICAL], E_PARSE => [null, LogLevel::CRITICAL], E_ERROR => [null, LogLevel::CRITICAL], E_CORE_ERROR => [null, LogLevel::CRITICAL], ]; private $thrownErrors = 0x1FFF; private $scopedErrors = 0x1FFF; private $tracedErrors = 0x77FB; private $screamedErrors = 0x55; private $loggedErrors = 0; private $traceReflector; private $isRecursive = 0; private $isRoot = false; private $exceptionHandler; private $bootstrappingLogger; private static $reservedMemory; private static $stackedErrors = []; private static $stackedErrorLevels = []; private static $toStringException = null; private static $silencedErrorCache = []; private static $silencedErrorCount = 0; private static $exitCode = 0; public static function register(self $handler = null, $replace = true) { if (null === self::$reservedMemory) { self::$reservedMemory = str_repeat('x', 10240); register_shutdown_function(__CLASS__.'::handleFatalError'); } if ($handlerIsNew = null === $handler) { $handler = new static(); } if (null === $prev = set_error_handler([$handler, 'handleError'])) { restore_error_handler(); set_error_handler([$handler, 'handleError'], $handler->thrownErrors | $handler->loggedErrors); $handler->isRoot = true; } if ($handlerIsNew && \is_array($prev) && $prev[0] instanceof self) { $handler = $prev[0]; $replace = false; } if (!$replace && $prev) { restore_error_handler(); $handlerIsRegistered = \is_array($prev) && $handler === $prev[0]; } else { $handlerIsRegistered = true; } if (\is_array($prev = set_exception_handler([$handler, 'handleException'])) && $prev[0] instanceof self) { restore_exception_handler(); if (!$handlerIsRegistered) { $handler = $prev[0]; } elseif ($handler !== $prev[0] && $replace) { set_exception_handler([$handler, 'handleException']); $p = $prev[0]->setExceptionHandler(null); $handler->setExceptionHandler($p); $prev[0]->setExceptionHandler($p); } } else { $handler->setExceptionHandler($prev); } $handler->throwAt(E_ALL & $handler->thrownErrors, true); return $handler; } public function __construct(BufferingLogger $bootstrappingLogger = null) { if ($bootstrappingLogger) { $this->bootstrappingLogger = $bootstrappingLogger; $this->setDefaultLogger($bootstrappingLogger); } $this->traceReflector = new \ReflectionProperty('Exception', 'trace'); $this->traceReflector->setAccessible(true); } public function setDefaultLogger(LoggerInterface $logger, $levels = E_ALL, $replace = false) { $loggers = []; if (\is_array($levels)) { foreach ($levels as $type => $logLevel) { if (empty($this->loggers[$type][0]) || $replace || $this->loggers[$type][0] === $this->bootstrappingLogger) { $loggers[$type] = [$logger, $logLevel]; } } } else { if (null === $levels) { $levels = E_ALL; } foreach ($this->loggers as $type => $log) { if (($type & $levels) && (empty($log[0]) || $replace || $log[0] === $this->bootstrappingLogger)) { $log[0] = $logger; $loggers[$type] = $log; } } } $this->setLoggers($loggers); } public function setLoggers(array $loggers) { $prevLogged = $this->loggedErrors; $prev = $this->loggers; $flush = []; foreach ($loggers as $type => $log) { if (!isset($prev[$type])) { throw new \InvalidArgumentException('Unknown error type: '.$type); } if (!\is_array($log)) { $log = [$log]; } elseif (!\array_key_exists(0, $log)) { throw new \InvalidArgumentException('No logger provided.'); } if (null === $log[0]) { $this->loggedErrors &= ~$type; } elseif ($log[0] instanceof LoggerInterface) { $this->loggedErrors |= $type; } else { throw new \InvalidArgumentException('Invalid logger provided.'); } $this->loggers[$type] = $log + $prev[$type]; if ($this->bootstrappingLogger && $prev[$type][0] === $this->bootstrappingLogger) { $flush[$type] = $type; } } $this->reRegister($prevLogged | $this->thrownErrors); if ($flush) { foreach ($this->bootstrappingLogger->cleanLogs() as $log) { $type = $log[2]['exception'] instanceof \ErrorException ? $log[2]['exception']->getSeverity() : E_ERROR; if (!isset($flush[$type])) { $this->bootstrappingLogger->log($log[0], $log[1], $log[2]); } elseif ($this->loggers[$type][0]) { $this->loggers[$type][0]->log($this->loggers[$type][1], $log[1], $log[2]); } } } return $prev; } public function setExceptionHandler(callable $handler = null) { $prev = $this->exceptionHandler; $this->exceptionHandler = $handler; return $prev; } public function throwAt($levels, $replace = false) { $prev = $this->thrownErrors; $this->thrownErrors = ($levels | E_RECOVERABLE_ERROR | E_USER_ERROR) & ~E_USER_DEPRECATED & ~E_DEPRECATED; if (!$replace) { $this->thrownErrors |= $prev; } $this->reRegister($prev | $this->loggedErrors); return $prev; } public function scopeAt($levels, $replace = false) { $prev = $this->scopedErrors; $this->scopedErrors = (int) $levels; if (!$replace) { $this->scopedErrors |= $prev; } return $prev; } public function traceAt($levels, $replace = false) { $prev = $this->tracedErrors; $this->tracedErrors = (int) $levels; if (!$replace) { $this->tracedErrors |= $prev; } return $prev; } /** * Sets the error levels where the @-operator is ignored. * * @param int $levels A bit field of E_* constants for screamed errors * @param bool $replace Replace or amend the previous value * * @return int The previous value */ public function screamAt($levels, $replace = false) { $prev = $this->screamedErrors; $this->screamedErrors = (int) $levels; if (!$replace) { $this->screamedErrors |= $prev; } return $prev; } private function reRegister($prev) { if ($prev !== $this->thrownErrors | $this->loggedErrors) { $handler = set_error_handler('var_dump'); $handler = \is_array($handler) ? $handler[0] : null; restore_error_handler(); if ($handler === $this) { restore_error_handler(); if ($this->isRoot) { set_error_handler([$this, 'handleError'], $this->thrownErrors | $this->loggedErrors); } else { set_error_handler([$this, 'handleError']); } } } } public function handleError($type, $message, $file, $line) { if (\PHP_VERSION_ID >= 70300 && E_WARNING === $type && '"' === $message[0] && false !== strpos($message, '" targeting switch is equivalent to "break')) { $type = E_DEPRECATED; } $level = error_reporting(); $silenced = 0 === ($level & $type); $level |= E_RECOVERABLE_ERROR | E_USER_ERROR | E_DEPRECATED | E_USER_DEPRECATED; $log = $this->loggedErrors & $type; $throw = $this->thrownErrors & $type & $level; $type &= $level | $this->screamedErrors; if (!$type || (!$log && !$throw)) { return !$silenced && $type && $log; } $scope = $this->scopedErrors & $type; if (4 < $numArgs = \func_num_args()) { $context = $scope ? (func_get_arg(4) ?: []) : []; $backtrace = 5 < $numArgs ? func_get_arg(5) : null; } else { $context = []; $backtrace = null; } if (isset($context['GLOBALS']) && $scope) { $e = $context; unset($e['GLOBALS'], $context); $context = $e; } if (null !== $backtrace && $type & E_ERROR) { $this->handleFatalError(compact('type', 'message', 'file', 'line', 'backtrace')); return true; } $logMessage = $this->levels[$type].': '.$message; if (null !== self::$toStringException) { $errorAsException = self::$toStringException; self::$toStringException = null; } elseif (!$throw && !($type & $level)) { if (!isset(self::$silencedErrorCache[$id = $file.':'.$line])) { $lightTrace = $this->tracedErrors & $type ? $this->cleanTrace(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3), $type, $file, $line, false) : []; $errorAsException = new SilencedErrorContext($type, $file, $line, $lightTrace); } elseif (isset(self::$silencedErrorCache[$id][$message])) { $lightTrace = null; $errorAsException = self::$silencedErrorCache[$id][$message]; ++$errorAsException->count; } else { $lightTrace = []; $errorAsException = null; } if (100 < ++self::$silencedErrorCount) { self::$silencedErrorCache = $lightTrace = []; self::$silencedErrorCount = 1; } if ($errorAsException) { self::$silencedErrorCache[$id][$message] = $errorAsException; } if (null === $lightTrace) { return true; } } else { if ($scope) { $errorAsException = new ContextErrorException($logMessage, 0, $type, $file, $line, $context); } else { $errorAsException = new \ErrorException($logMessage, 0, $type, $file, $line); } if ($throw || $this->tracedErrors & $type) { $backtrace = $backtrace ?: $errorAsException->getTrace(); $lightTrace = $this->cleanTrace($backtrace, $type, $file, $line, $throw); $this->traceReflector->setValue($errorAsException, $lightTrace); } else { $this->traceReflector->setValue($errorAsException, []); } } if ($throw) { if (\PHP_VERSION_ID < 70400 && E_USER_ERROR & $type) { for ($i = 1; isset($backtrace[$i]); ++$i) { if (isset($backtrace[$i]['function'], $backtrace[$i]['type'], $backtrace[$i - 1]['function']) && '__toString' === $backtrace[$i]['function'] && '->' === $backtrace[$i]['type'] && !isset($backtrace[$i - 1]['class']) && ('trigger_error' === $backtrace[$i - 1]['function'] || 'user_error' === $backtrace[$i - 1]['function']) ) { foreach ($context as $e) { if (($e instanceof \Exception || $e instanceof \Throwable) && $e->__toString() === $message) { if (1 === $i) { $errorAsException = $e; break; } self::$toStringException = $e; return true; } } if (1 < $i) { $this->handleException($errorAsException); return false; } } } } throw $errorAsException; } if ($this->isRecursive) { $log = 0; } elseif (self::$stackedErrorLevels) { self::$stackedErrors[] = [ $this->loggers[$type][0], ($type & $level) ? $this->loggers[$type][1] : LogLevel::DEBUG, $logMessage, $errorAsException ? ['exception' => $errorAsException] : [], ]; } else { if (\PHP_VERSION_ID < (\PHP_VERSION_ID < 70400 ? 70316 : 70404) && !\defined('HHVM_VERSION')) { $currentErrorHandler = set_error_handler('var_dump'); restore_error_handler(); } try { $this->isRecursive = true; $level = ($type & $level) ? $this->loggers[$type][1] : LogLevel::DEBUG; $this->loggers[$type][0]->log($level, $logMessage, $errorAsException ? ['exception' => $errorAsException] : []); } finally { $this->isRecursive = false; if (\PHP_VERSION_ID < (\PHP_VERSION_ID < 70400 ? 70316 : 70404) && !\defined('HHVM_VERSION')) { set_error_handler($currentErrorHandler); } } } return !$silenced && $type && $log; } public function handleException($exception, array $error = null) { if (null === $error) { self::$exitCode = 255; } if (!$exception instanceof \Exception) { $exception = new FatalThrowableError($exception); } $type = $exception instanceof FatalErrorException ? $exception->getSeverity() : E_ERROR; $handlerException = null; if (($this->loggedErrors & $type) || $exception instanceof FatalThrowableError) { if ($exception instanceof FatalErrorException) { if ($exception instanceof FatalThrowableError) { $error = [ 'type' => $type, 'message' => $message = $exception->getMessage(), 'file' => $exception->getFile(), 'line' => $exception->getLine(), ]; } else { $message = 'Fatal '.$exception->getMessage(); } } elseif ($exception instanceof \ErrorException) { $message = 'Uncaught '.$exception->getMessage(); } else { $message = 'Uncaught Exception: '.$exception->getMessage(); } } if ($this->loggedErrors & $type) { try { $this->loggers[$type][0]->log($this->loggers[$type][1], $message, ['exception' => $exception]); } catch (\Exception $handlerException) { } catch (\Throwable $handlerException) { } } if ($exception instanceof FatalErrorException && !$exception instanceof OutOfMemoryException && $error) { foreach ($this->getFatalErrorHandlers() as $handler) { if ($e = $handler->handleError($error, $exception)) { $exception = $e; break; } } } $exceptionHandler = $this->exceptionHandler; $this->exceptionHandler = null; try { if (null !== $exceptionHandler) { $exceptionHandler($exception); return; } $handlerException = $handlerException ?: $exception; } catch (\Exception $handlerException) { } catch (\Throwable $handlerException) { } if ($exception === $handlerException) { self::$reservedMemory = null; throw $exception; } $this->handleException($handlerException); } public static function handleFatalError(array $error = null) { if (null === self::$reservedMemory) { return; } $handler = self::$reservedMemory = null; $handlers = []; $previousHandler = null; $sameHandlerLimit = 10; while (!\is_array($handler) || !$handler[0] instanceof self) { $handler = set_exception_handler('var_dump'); restore_exception_handler(); if (!$handler) { break; } restore_exception_handler(); if ($handler !== $previousHandler) { array_unshift($handlers, $handler); $previousHandler = $handler; } elseif (0 === --$sameHandlerLimit) { $handler = null; break; } } foreach ($handlers as $h) { set_exception_handler($h); } if (!$handler) { return; } if ($handler !== $h) { $handler[0]->setExceptionHandler($h); } $handler = $handler[0]; $handlers = []; if ($exit = null === $error) { $error = error_get_last(); } try { while (self::$stackedErrorLevels) { static::unstackErrors(); } } catch (\Exception $exception) { } catch (\Throwable $exception) { } if ($error && $error['type'] &= E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR) { $handler->throwAt(0, true); $trace = isset($error['backtrace']) ? $error['backtrace'] : null; if (0 === strpos($error['message'], 'Allowed memory') || 0 === strpos($error['message'], 'Out of memory')) { $exception = new OutOfMemoryException($handler->levels[$error['type']].': '.$error['message'], 0, $error['type'], $error['file'], $error['line'], 2, false, $trace); } else { $exception = new FatalErrorException($handler->levels[$error['type']].': '.$error['message'], 0, $error['type'], $error['file'], $error['line'], 2, true, $trace); } } try { if (isset($exception)) { self::$exitCode = 255; $handler->handleException($exception, $error); } } catch (FatalErrorException $e) { } if ($exit && self::$exitCode) { $exitCode = self::$exitCode; register_shutdown_function('register_shutdown_function', function () use ($exitCode) { exit($exitCode); }); } } public static function stackErrors() { @trigger_error('Support for stacking errors is deprecated since Symfony 3.4 and will be removed in 4.0.', E_USER_DEPRECATED); self::$stackedErrorLevels[] = error_reporting(error_reporting() | E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR); } public static function unstackErrors() { @trigger_error('Support for unstacking errors is deprecated since Symfony 3.4 and will be removed in 4.0.', E_USER_DEPRECATED); $level = array_pop(self::$stackedErrorLevels); if (null !== $level) { $errorReportingLevel = error_reporting($level); if ($errorReportingLevel !== ($level | E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR)) { error_reporting($errorReportingLevel); } } if (empty(self::$stackedErrorLevels)) { $errors = self::$stackedErrors; self::$stackedErrors = []; foreach ($errors as $error) { $error[0]->log($error[1], $error[2], $error[3]); } } } protected function getFatalErrorHandlers() { return [ new UndefinedFunctionFatalErrorHandler(), new UndefinedMethodFatalErrorHandler(), new ClassNotFoundFatalErrorHandler(), ]; } private function cleanTrace($backtrace, $type, $file, $line, $throw) { $lightTrace = $backtrace; for ($i = 0; isset($backtrace[$i]); ++$i) { if (isset($backtrace[$i]['file'], $backtrace[$i]['line']) && $backtrace[$i]['line'] === $line && $backtrace[$i]['file'] === $file) { $lightTrace = \array_slice($lightTrace, 1 + $i); break; } } if (!($throw || $this->scopedErrors & $type)) { for ($i = 0; isset($lightTrace[$i]); ++$i) { unset($lightTrace[$i]['args'], $lightTrace[$i]['object']); } } return $lightTrace; } } logs[] = [$level, $message, $context]; } public function cleanLogs() { $logs = $this->logs; $this->logs = []; return $logs; } } throwAt(0, true); } DebugClassLoader::enable(); } } getCode(), $previous->getSeverity(), $previous->getFile(), $previous->getLine(), null, true, null, $previous->getPrevious() ); $this->setTrace($previous->getTrace()); } } severity = $severity; $this->file = $file; $this->line = $line; $this->trace = $trace; $this->count = $count; } public function getSeverity() { return $this->severity; } public function getFile() { return $this->file; } public function getLine() { return $this->line; } public function getTrace() { return $this->trace; } public function jsonSerialize() { return [ 'severity' => $this->severity, 'file' => $this->file, 'line' => $this->line, 'trace' => $this->trace, 'count' => $this->count, ]; } } context = $context; } public function getContext() { @trigger_error(sprintf('The %s class is deprecated since Symfony 3.3 and will be removed in 4.0.', __CLASS__), E_USER_DEPRECATED); return $this->context; } } getCode(), $previous->getSeverity(), $previous->getFile(), $previous->getLine(), null, true, null, $previous->getPrevious() ); $this->setTrace($previous->getTrace()); } } setTrace($trace); } elseif (null !== $traceOffset) { if (\function_exists('xdebug_get_function_stack')) { $trace = xdebug_get_function_stack(); if (0 < $traceOffset) { array_splice($trace, -$traceOffset); } foreach ($trace as &$frame) { if (!isset($frame['type'])) { if (isset($frame['class'])) { $frame['type'] = '::'; } } elseif ('dynamic' === $frame['type']) { $frame['type'] = '->'; } elseif ('static' === $frame['type']) { $frame['type'] = '::'; } if (!$traceArgs) { unset($frame['params'], $frame['args']); } elseif (isset($frame['params']) && !isset($frame['args'])) { $frame['args'] = $frame['params']; unset($frame['params']); } } unset($frame); $trace = array_reverse($trace); } elseif (\function_exists('symfony_debug_backtrace')) { $trace = symfony_debug_backtrace(); if (0 < $traceOffset) { array_splice($trace, 0, $traceOffset); } } else { $trace = []; } $this->setTrace($trace); } } protected function setTrace($trace) { $traceReflector = new \ReflectionProperty('Exception', 'trace'); $traceReflector->setAccessible(true); $traceReflector->setValue($this, $trace); } } getMessage(); $severity = E_PARSE; } elseif ($e instanceof \TypeError) { $message = 'Type error: '.$e->getMessage(); $severity = E_RECOVERABLE_ERROR; } else { $message = $e->getMessage(); $severity = E_ERROR; } \ErrorException::__construct( $message, $e->getCode(), $severity, $e->getFile(), $e->getLine(), $e->getPrevious() ); $this->setTrace($e->getTrace()); } } setMessage($exception->getMessage()); $e->setCode($exception->getCode()); if ($exception instanceof HttpExceptionInterface) { $statusCode = $exception->getStatusCode(); $headers = array_merge($headers, $exception->getHeaders()); } elseif ($exception instanceof RequestExceptionInterface) { $statusCode = 400; } if (null === $statusCode) { $statusCode = 500; } $e->setStatusCode($statusCode); $e->setHeaders($headers); $e->setTraceFromException($exception); $e->setClass(\get_class($exception)); $e->setFile($exception->getFile()); $e->setLine($exception->getLine()); $previous = $exception->getPrevious(); if ($previous instanceof \Exception) { $e->setPrevious(static::create($previous)); } elseif ($previous instanceof \Throwable) { $e->setPrevious(static::create(new FatalThrowableError($previous))); } return $e; } public function toArray() { $exceptions = []; foreach (array_merge([$this], $this->getAllPrevious()) as $exception) { $exceptions[] = [ 'message' => $exception->getMessage(), 'class' => $exception->getClass(), 'trace' => $exception->getTrace(), ]; } return $exceptions; } public function getStatusCode() { return $this->statusCode; } public function setStatusCode($code) { $this->statusCode = $code; } public function getHeaders() { return $this->headers; } public function setHeaders(array $headers) { $this->headers = $headers; } public function getClass() { return $this->class; } public function setClass($class) { $this->class = $class; } public function getFile() { return $this->file; } public function setFile($file) { $this->file = $file; } public function getLine() { return $this->line; } public function setLine($line) { $this->line = $line; } public function getMessage() { return $this->message; } public function setMessage($message) { $this->message = $message; } public function getCode() { return $this->code; } public function setCode($code) { $this->code = $code; } public function getPrevious() { return $this->previous; } public function setPrevious(self $previous) { $this->previous = $previous; } public function getAllPrevious() { $exceptions = []; $e = $this; while ($e = $e->getPrevious()) { $exceptions[] = $e; } return $exceptions; } public function getTrace() { return $this->trace; } public function setTraceFromException(\Exception $exception) { $this->setTrace($exception->getTrace(), $exception->getFile(), $exception->getLine()); } public function setTrace($trace, $file, $line) { $this->trace = []; $this->trace[] = [ 'namespace' => '', 'short_class' => '', 'class' => '', 'type' => '', 'function' => '', 'file' => $file, 'line' => $line, 'args' => [], ]; foreach ($trace as $entry) { $class = ''; $namespace = ''; if (isset($entry['class'])) { $parts = explode('\\', $entry['class']); $class = array_pop($parts); $namespace = implode('\\', $parts); } $this->trace[] = [ 'namespace' => $namespace, 'short_class' => $class, 'class' => isset($entry['class']) ? $entry['class'] : '', 'type' => isset($entry['type']) ? $entry['type'] : '', 'function' => isset($entry['function']) ? $entry['function'] : null, 'file' => isset($entry['file']) ? $entry['file'] : null, 'line' => isset($entry['line']) ? $entry['line'] : null, 'args' => isset($entry['args']) ? $this->flattenArgs($entry['args']) : [], ]; } } private function flattenArgs($args, $level = 0, &$count = 0) { $result = []; foreach ($args as $key => $value) { if (++$count > 1e4) { return ['array', '*SKIPPED over 10000 entries*']; } if ($value instanceof \__PHP_Incomplete_Class) { $result[$key] = ['incomplete-object', $this->getClassNameFromIncomplete($value)]; } elseif (\is_object($value)) { $result[$key] = ['object', \get_class($value)]; } elseif (\is_array($value)) { if ($level > 10) { $result[$key] = ['array', '*DEEP NESTED ARRAY*']; } else { $result[$key] = ['array', $this->flattenArgs($value, $level + 1, $count)]; } } elseif (null === $value) { $result[$key] = ['null', null]; } elseif (\is_bool($value)) { $result[$key] = ['boolean', $value]; } elseif (\is_int($value)) { $result[$key] = ['integer', $value]; } elseif (\is_float($value)) { $result[$key] = ['float', $value]; } elseif (\is_resource($value)) { $result[$key] = ['resource', get_resource_type($value)]; } else { $result[$key] = ['string', (string) $value]; } } return $result; } private function getClassNameFromIncomplete(\__PHP_Incomplete_Class $value) { $array = new \ArrayObject($value); return $array['__PHP_Incomplete_Class_Name']; } } getCode(), $previous->getSeverity(), $previous->getFile(), $previous->getLine(), null, true, null, $previous->getPrevious() ); $this->setTrace($previous->getTrace()); } } ignore = static::IGNORE_VCS_FILES | static::IGNORE_DOT_FILES; } public static function create() { return new static(); } public function directories() { $this->mode = Iterator\FileTypeFilterIterator::ONLY_DIRECTORIES; return $this; } public function files() { $this->mode = Iterator\FileTypeFilterIterator::ONLY_FILES; return $this; } public function depth($level) { $this->depths[] = new Comparator\NumberComparator($level); return $this; } public function date($date) { $this->dates[] = new Comparator\DateComparator($date); return $this; } public function name($pattern) { $this->names[] = $pattern; return $this; } public function notName($pattern) { $this->notNames[] = $pattern; return $this; } public function contains($pattern) { $this->contains[] = $pattern; return $this; } public function notContains($pattern) { $this->notContains[] = $pattern; return $this; } public function path($pattern) { $this->paths[] = $pattern; return $this; } public function notPath($pattern) { $this->notPaths[] = $pattern; return $this; } public function size($size) { $this->sizes[] = new Comparator\NumberComparator($size); return $this; } public function exclude($dirs) { $this->exclude = array_merge($this->exclude, (array) $dirs); return $this; } public function ignoreDotFiles($ignoreDotFiles) { if ($ignoreDotFiles) { $this->ignore |= static::IGNORE_DOT_FILES; } else { $this->ignore &= ~static::IGNORE_DOT_FILES; } return $this; } public function ignoreVCS($ignoreVCS) { if ($ignoreVCS) { $this->ignore |= static::IGNORE_VCS_FILES; } else { $this->ignore &= ~static::IGNORE_VCS_FILES; } return $this; } public static function addVCSPattern($pattern) { foreach ((array) $pattern as $p) { self::$vcsPatterns[] = $p; } self::$vcsPatterns = array_unique(self::$vcsPatterns); } public function sort(\Closure $closure) { $this->sort = $closure; return $this; } public function sortByName() { $this->sort = Iterator\SortableIterator::SORT_BY_NAME; return $this; } public function sortByType() { $this->sort = Iterator\SortableIterator::SORT_BY_TYPE; return $this; } public function sortByAccessedTime() { $this->sort = Iterator\SortableIterator::SORT_BY_ACCESSED_TIME; return $this; } public function sortByChangedTime() { $this->sort = Iterator\SortableIterator::SORT_BY_CHANGED_TIME; return $this; } public function sortByModifiedTime() { $this->sort = Iterator\SortableIterator::SORT_BY_MODIFIED_TIME; return $this; } public function filter(\Closure $closure) { $this->filters[] = $closure; return $this; } public function followLinks() { $this->followLinks = true; return $this; } public function ignoreUnreadableDirs($ignore = true) { $this->ignoreUnreadableDirs = (bool) $ignore; return $this; } 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 \InvalidArgumentException(sprintf('The "%s" directory does not exist.', $dir)); } } $this->dirs = array_merge($this->dirs, $resolvedDirs); return $this; } 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; } public function append($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; } public function hasResults() { foreach ($this->getIterator() as $_) { return true; } return false; } public function count() { return iterator_count($this->getIterator()); } private function searchInDirectory($dir) { $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[] = '#(^|/)\..+(/|$)#'; } $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) { $iteratorAggregate = new Iterator\SortableIterator($iterator, $this->sort); $iterator = $iteratorAggregate->getIterator(); } return $iterator; } private function normalizeDir($dir) { if ('/' === $dir) { return $dir; } $dir = rtrim($dir, '/'.\DIRECTORY_SEPARATOR); if (preg_match('#^(ssh2\.)?s?ftp://#', $dir)) { $dir .= '/'; } return $dir; } } relativePath = $relativePath; $this->relativePathname = $relativePathname; } public function getRelativePath() { return $this->relativePath; } public function getRelativePathname() { return $this->relativePathname; } 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; } } ]=?|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); } } ]=?)?\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])) { 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] : '=='); } } target; } public function setTarget($target) { $this->target = $target; } public function getOperator() { return $this->operator; } public function setOperator($operator) { if (!$operator) { $operator = '=='; } if (!\in_array($operator, ['>', '<', '>=', '<=', '==', '!='])) { throw new \InvalidArgumentException(sprintf('Invalid operator "%s".', $operator)); } $this->operator = $operator; } 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; } } isAccepted($this->current()->getFilename()); } protected function toRegex($str) { return $this->isRegex($str) ? $str : Glob::toRegex($str); } } 50607 || (\PHP_VERSION_ID > 50523 && \PHP_VERSION_ID < 50600)) { parent::rewind(); return; } $iterator = $this; while ($iterator instanceof \OuterIterator) { $innerIterator = $iterator->getInnerIterator(); if ($innerIterator instanceof RecursiveDirectoryIterator) { if ($innerIterator->isRewindable()) { $innerIterator->next(); $innerIterator->rewind(); } } elseif ($innerIterator instanceof \FilesystemIterator) { $innerIterator->next(); $innerIterator->rewind(); } $iterator = $innerIterator; } parent::rewind(); } } minDepth = $minDepth; $iterator->setMaxDepth(PHP_INT_MAX === $maxDepth ? -1 : $maxDepth); parent::__construct($iterator); } public function accept() { return $this->getInnerIterator()->getDepth() >= $this->minDepth; } } ignoreUnreadableDirs = $ignoreUnreadableDirs; $this->rootPath = $path; if ('/' !== \DIRECTORY_SEPARATOR && !($flags & self::UNIX_PATHS)) { $this->directorySeparator = \DIRECTORY_SEPARATOR; } } public function current() { 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); } public function getChildren() { try { $children = parent::getChildren(); if ($children instanceof self) { $children->ignoreUnreadableDirs = $this->ignoreUnreadableDirs; $children->rewindable = &$this->rewindable; $children->rootPath = $this->rootPath; } return $children; } catch (\UnexpectedValueException $e) { if ($this->ignoreUnreadableDirs) { return new \RecursiveArrayIterator([]); } else { throw new AccessDeniedException($e->getMessage(), $e->getCode(), $e); } } } public function rewind() { if (false === $this->isRewindable()) { return; } if (\PHP_VERSION_ID < 50523 || \PHP_VERSION_ID >= 50600 && \PHP_VERSION_ID < 50607) { parent::next(); } parent::rewind(); } public function isRewindable() { if (null !== $this->rewindable) { return $this->rewindable; } if ('' === $this->getPath()) { return $this->rewindable = false; } 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; } } comparators = $comparators; parent::__construct($iterator); } 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; } } matchRegexps[] = $this->toRegex($pattern); } foreach ($noMatchPatterns as $pattern) { $this->noMatchRegexps[] = $this->toRegex($pattern); } parent::__construct($iterator); } protected function isAccepted($string) { foreach ($this->noMatchRegexps as $regex) { if (preg_match($regex, $string)) { return false; } } if ($this->matchRegexps) { foreach ($this->matchRegexps as $regex) { if (preg_match($regex, $string)) { return true; } } return false; } return true; } protected function isRegex($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; } abstract protected function toRegex($str); } filters = $filters; parent::__construct($iterator); } public function accept() { $fileinfo = $this->current(); foreach ($this->filters as $filter) { if (false === \call_user_func($filter, $fileinfo)) { return false; } } return true; } } comparators = $comparators; parent::__construct($iterator); } 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; } } 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); } 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; } 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; } } iterator = $iterator; if (self::SORT_BY_NAME === $sort) { $this->sort = static function ($a, $b) { return strcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname()); }; } elseif (self::SORT_BY_TYPE === $sort) { $this->sort = static function ($a, $b) { if ($a->isDir() && $b->isFile()) { return -1; } elseif ($a->isFile() && $b->isDir()) { return 1; } return strcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname()); }; } elseif (self::SORT_BY_ACCESSED_TIME === $sort) { $this->sort = static function ($a, $b) { return $a->getATime() - $b->getATime(); }; } elseif (self::SORT_BY_CHANGED_TIME === $sort) { $this->sort = static function ($a, $b) { return $a->getCTime() - $b->getCTime(); }; } elseif (self::SORT_BY_MODIFIED_TIME === $sort) { $this->sort = static function ($a, $b) { return $a->getMTime() - $b->getMTime(); }; } elseif (\is_callable($sort)) { $this->sort = $sort; } else { throw new \InvalidArgumentException('The SortableIterator takes a PHP callable or a valid built-in sort algorithm as an argument.'); } } public function getIterator() { $array = iterator_to_array($this->iterator, true); uasort($array, $this->sort); return new \ArrayIterator($array); } } 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); } protected function toRegex($str) { return $this->isRegex($str) ? $str : '/'.preg_quote($str, '/').'/'; } } current()->getRelativePathname(); if ('\\' === \DIRECTORY_SEPARATOR) { $filename = str_replace('\\', '/', $filename); } return $this->isAccepted($filename); } protected function toRegex($str) { return $this->isRegex($str) ? $str : '/'.preg_quote($str, '/').'/'; } } mode = $mode; parent::__construct($iterator); } 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; } } 255) { return (string) $int; } if ($int < 0) { $int += 256; } return \chr($int); } } $callback) { $result = preg_replace_callback($pattern, $callback, $result, $limit, $c); $count += $c; } return $result; } public static function error_clear_last() { static $handler; if (!$handler) { $handler = function () { return false; }; } set_error_handler($handler); @trigger_error(''); restore_error_handler(); } private static function intArg($value, $caller, $pos) { if (\is_int($value)) { return $value; } if (!\is_numeric($value) || PHP_INT_MAX <= ($value += 0) || ~PHP_INT_MAX >= $value) { throw new \TypeError(sprintf('%s() expects parameter %d to be integer, %s given', $caller, $pos, \gettype($value))); } return (int) $value; } } morePrecision = $morePrecision; $this->reset(); } public function getSections() { return $this->sections; } public function openSection($id = null) { $current = end($this->activeSections); if (null !== $id && null === $current->get($id)) { throw new \LogicException(sprintf('The section "%s" has been started at an other level and can not be opened.', $id)); } $this->start('__section__.child', 'section'); $this->activeSections[] = $current->open($id); $this->start('__section__'); } public function stopSection($id) { $this->stop('__section__'); if (1 == \count($this->activeSections)) { throw new \LogicException('There is no started section to stop.'); } $this->sections[$id] = array_pop($this->activeSections)->setId($id); $this->stop('__section__.child'); } public function start($name, $category = null) { return end($this->activeSections)->startEvent($name, $category); } public function isStarted($name) { return end($this->activeSections)->isEventStarted($name); } public function stop($name) { return end($this->activeSections)->stopEvent($name); } public function lap($name) { return end($this->activeSections)->stopEvent($name)->start(); } public function getEvent($name) { return end($this->activeSections)->getEvent($name); } public function getSectionEvents($id) { return isset($this->sections[$id]) ? $this->sections[$id]->getEvents() : []; } public function reset() { $this->sections = $this->activeSections = ['__root__' => new Section(null, $this->morePrecision)]; } } origin = $this->formatTime($origin); $this->category = \is_string($category) ? $category : 'default'; $this->morePrecision = $morePrecision; } public function getCategory() { return $this->category; } public function getOrigin() { return $this->origin; } public function start() { $this->started[] = $this->getNow(); return $this; } public function stop() { if (!\count($this->started)) { throw new \LogicException('stop() called but start() has not been called before.'); } $this->periods[] = new StopwatchPeriod(array_pop($this->started), $this->getNow(), $this->morePrecision); return $this; } public function isStarted() { return !empty($this->started); } public function lap() { return $this->stop()->start(); } public function ensureStopped() { while (\count($this->started)) { $this->stop(); } } public function getPeriods() { return $this->periods; } public function getStartTime() { if (isset($this->periods[0])) { return $this->periods[0]->getStartTime(); } if ($this->started) { return $this->started[0]; } return 0; } public function getEndTime() { $count = \count($this->periods); return $count ? $this->periods[$count - 1]->getEndTime() : 0; } public function getDuration() { $periods = $this->periods; $left = \count($this->started); for ($i = $left - 1; $i >= 0; --$i) { $periods[] = new StopwatchPeriod($this->started[$i], $this->getNow(), $this->morePrecision); } $total = 0; foreach ($periods as $period) { $total += $period->getDuration(); } return $total; } public function getMemory() { $memory = 0; foreach ($this->periods as $period) { if ($period->getMemory() > $memory) { $memory = $period->getMemory(); } } return $memory; } protected function getNow() { return $this->formatTime(microtime(true) * 1000 - $this->origin); } private function formatTime($time) { if (!is_numeric($time)) { throw new \InvalidArgumentException('The time must be a numerical value.'); } return round($time, 1); } public function __toString() { return sprintf('%s: %.2F MiB - %d ms', $this->getCategory(), $this->getMemory() / 1024 / 1024, $this->getDuration()); } } origin = is_numeric($origin) ? $origin : null; $this->morePrecision = $morePrecision; } public function get($id) { foreach ($this->children as $child) { if ($id === $child->getId()) { return $child; } } return null; } public function open($id) { if (null === $session = $this->get($id)) { $session = $this->children[] = new self(microtime(true) * 1000, $this->morePrecision); } return $session; } public function getId() { return $this->id; } public function setId($id) { $this->id = $id; return $this; } public function startEvent($name, $category) { if (!isset($this->events[$name])) { $this->events[$name] = new StopwatchEvent($this->origin ?: microtime(true) * 1000, $category, $this->morePrecision); } return $this->events[$name]->start(); } public function isEventStarted($name) { return isset($this->events[$name]) && $this->events[$name]->isStarted(); } public function stopEvent($name) { if (!isset($this->events[$name])) { throw new \LogicException(sprintf('Event "%s" is not started.', $name)); } return $this->events[$name]->stop(); } public function lap($name) { return $this->stopEvent($name)->start(); } public function getEvent($name) { if (!isset($this->events[$name])) { throw new \LogicException(sprintf('Event "%s" is not known.', $name)); } return $this->events[$name]; } public function getEvents() { return $this->events; } } start = $morePrecision ? (float) $start : (int) $start; $this->end = $morePrecision ? (float) $end : (int) $end; $this->memory = memory_get_usage(true); } public function getStartTime() { return $this->start; } public function getEndTime() { return $this->end; } public function getDuration() { return $this->end - $this->start; } public function getMemory() { return $this->memory; } } 'bool', 'integer' => 'int', 'double' => 'float', ]; public function setDefault($option, $value) { if ($this->locked) { throw new AccessException('Default values cannot be set from a lazy option or normalizer.'); } if ($value instanceof \Closure) { $reflClosure = new \ReflectionFunction($value); $params = $reflClosure->getParameters(); if (isset($params[0]) && null !== ($class = $params[0]->getClass()) && Options::class === $class->name) { if (!isset($this->defaults[$option])) { $this->defaults[$option] = null; } if (!isset($this->lazy[$option]) || !isset($params[1])) { $this->lazy[$option] = []; } $this->lazy[$option][] = $value; $this->defined[$option] = true; unset($this->resolved[$option]); return $this; } } unset($this->lazy[$option]); if (!isset($this->defined[$option]) || \array_key_exists($option, $this->resolved)) { $this->resolved[$option] = $value; } $this->defaults[$option] = $value; $this->defined[$option] = true; return $this; } public function setDefaults(array $defaults) { foreach ($defaults as $option => $value) { $this->setDefault($option, $value); } return $this; } public function hasDefault($option) { return \array_key_exists($option, $this->defaults); } public function setRequired($optionNames) { if ($this->locked) { throw new AccessException('Options cannot be made required from a lazy option or normalizer.'); } foreach ((array) $optionNames as $option) { $this->defined[$option] = true; $this->required[$option] = true; } return $this; } public function isRequired($option) { return isset($this->required[$option]); } public function getRequiredOptions() { return array_keys($this->required); } public function isMissing($option) { return isset($this->required[$option]) && !\array_key_exists($option, $this->defaults); } public function getMissingOptions() { return array_keys(array_diff_key($this->required, $this->defaults)); } public function setDefined($optionNames) { if ($this->locked) { throw new AccessException('Options cannot be defined from a lazy option or normalizer.'); } foreach ((array) $optionNames as $option) { $this->defined[$option] = true; } return $this; } public function isDefined($option) { return isset($this->defined[$option]); } public function getDefinedOptions() { return array_keys($this->defined); } public function setNormalizer($option, \Closure $normalizer) { if ($this->locked) { throw new AccessException('Normalizers cannot be set from a lazy option or normalizer.'); } if (!isset($this->defined[$option])) { throw new UndefinedOptionsException(sprintf('The option "%s" does not exist. Defined options are: "%s".', $option, implode('", "', array_keys($this->defined)))); } $this->normalizers[$option] = $normalizer; unset($this->resolved[$option]); return $this; } public function setAllowedValues($option, $allowedValues) { if ($this->locked) { throw new AccessException('Allowed values cannot be set from a lazy option or normalizer.'); } if (!isset($this->defined[$option])) { throw new UndefinedOptionsException(sprintf('The option "%s" does not exist. Defined options are: "%s".', $option, implode('", "', array_keys($this->defined)))); } $this->allowedValues[$option] = \is_array($allowedValues) ? $allowedValues : [$allowedValues]; unset($this->resolved[$option]); return $this; } public function addAllowedValues($option, $allowedValues) { if ($this->locked) { throw new AccessException('Allowed values cannot be added from a lazy option or normalizer.'); } if (!isset($this->defined[$option])) { throw new UndefinedOptionsException(sprintf('The option "%s" does not exist. Defined options are: "%s".', $option, implode('", "', array_keys($this->defined)))); } if (!\is_array($allowedValues)) { $allowedValues = [$allowedValues]; } if (!isset($this->allowedValues[$option])) { $this->allowedValues[$option] = $allowedValues; } else { $this->allowedValues[$option] = array_merge($this->allowedValues[$option], $allowedValues); } unset($this->resolved[$option]); return $this; } public function setAllowedTypes($option, $allowedTypes) { if ($this->locked) { throw new AccessException('Allowed types cannot be set from a lazy option or normalizer.'); } if (!isset($this->defined[$option])) { throw new UndefinedOptionsException(sprintf('The option "%s" does not exist. Defined options are: "%s".', $option, implode('", "', array_keys($this->defined)))); } $this->allowedTypes[$option] = (array) $allowedTypes; unset($this->resolved[$option]); return $this; } public function addAllowedTypes($option, $allowedTypes) { if ($this->locked) { throw new AccessException('Allowed types cannot be added from a lazy option or normalizer.'); } if (!isset($this->defined[$option])) { throw new UndefinedOptionsException(sprintf('The option "%s" does not exist. Defined options are: "%s".', $option, implode('", "', array_keys($this->defined)))); } if (!isset($this->allowedTypes[$option])) { $this->allowedTypes[$option] = (array) $allowedTypes; } else { $this->allowedTypes[$option] = array_merge($this->allowedTypes[$option], (array) $allowedTypes); } unset($this->resolved[$option]); return $this; } public function remove($optionNames) { if ($this->locked) { throw new AccessException('Options cannot be removed from a lazy option or normalizer.'); } foreach ((array) $optionNames as $option) { unset($this->defined[$option], $this->defaults[$option], $this->required[$option], $this->resolved[$option]); unset($this->lazy[$option], $this->normalizers[$option], $this->allowedTypes[$option], $this->allowedValues[$option]); } return $this; } public function clear() { if ($this->locked) { throw new AccessException('Options cannot be cleared from a lazy option or normalizer.'); } $this->defined = []; $this->defaults = []; $this->required = []; $this->resolved = []; $this->lazy = []; $this->normalizers = []; $this->allowedTypes = []; $this->allowedValues = []; return $this; } public function resolve(array $options = []) { if ($this->locked) { throw new AccessException('Options cannot be resolved from a lazy option or normalizer.'); } $clone = clone $this; $diff = array_diff_key($options, $clone->defined); if (\count($diff) > 0) { ksort($clone->defined); ksort($diff); throw new UndefinedOptionsException(sprintf((\count($diff) > 1 ? 'The options "%s" do not exist.' : 'The option "%s" does not exist.').' Defined options are: "%s".', implode('", "', array_keys($diff)), implode('", "', array_keys($clone->defined)))); } foreach ($options as $option => $value) { $clone->defaults[$option] = $value; unset($clone->resolved[$option], $clone->lazy[$option]); } $diff = array_diff_key($clone->required, $clone->defaults); if (\count($diff) > 0) { ksort($diff); throw new MissingOptionsException(sprintf(\count($diff) > 1 ? 'The required options "%s" are missing.' : 'The required option "%s" is missing.', implode('", "', array_keys($diff)))); } $clone->locked = true; foreach ($clone->defaults as $option => $_) { $clone->offsetGet($option); } return $clone->resolved; } public function offsetGet($option) { if (!$this->locked) { throw new AccessException('Array access is only supported within closures of lazy options and normalizers.'); } if (\array_key_exists($option, $this->resolved)) { return $this->resolved[$option]; } if (!\array_key_exists($option, $this->defaults)) { if (!isset($this->defined[$option])) { throw new NoSuchOptionException(sprintf('The option "%s" does not exist. Defined options are: "%s".', $option, implode('", "', array_keys($this->defined)))); } throw new NoSuchOptionException(sprintf('The optional option "%s" has no value set. You should make sure it is set with "isset" before reading it.', $option)); } $value = $this->defaults[$option]; if (isset($this->lazy[$option])) { if (isset($this->calling[$option])) { throw new OptionDefinitionException(sprintf('The options "%s" have a cyclic dependency.', implode('", "', array_keys($this->calling)))); } $this->calling[$option] = true; try { foreach ($this->lazy[$option] as $closure) { $value = $closure($this, $value); } } finally { unset($this->calling[$option]); } } if (isset($this->allowedTypes[$option])) { $valid = true; $invalidTypes = []; foreach ($this->allowedTypes[$option] as $type) { $type = isset(self::$typeAliases[$type]) ? self::$typeAliases[$type] : $type; if ($valid = $this->verifyTypes($type, $value, $invalidTypes)) { break; } } if (!$valid) { $fmtActualValue = $this->formatValue($value); $fmtAllowedTypes = implode('" or "', $this->allowedTypes[$option]); $fmtProvidedTypes = implode('|', array_keys($invalidTypes)); $allowedContainsArrayType = \count(array_filter( $this->allowedTypes[$option], function ($item) { return '[]' === substr(isset(self::$typeAliases[$item]) ? self::$typeAliases[$item] : $item, -2); } )) > 0; if (\is_array($value) && $allowedContainsArrayType) { throw new InvalidOptionsException(sprintf('The option "%s" with value %s is expected to be of type "%s", but one of the elements is of type "%s".', $option, $fmtActualValue, $fmtAllowedTypes, $fmtProvidedTypes)); } throw new InvalidOptionsException(sprintf('The option "%s" with value %s is expected to be of type "%s", but is of type "%s".', $option, $fmtActualValue, $fmtAllowedTypes, $fmtProvidedTypes)); } } if (isset($this->allowedValues[$option])) { $success = false; $printableAllowedValues = []; foreach ($this->allowedValues[$option] as $allowedValue) { if ($allowedValue instanceof \Closure) { if ($allowedValue($value)) { $success = true; break; } continue; } if ($value === $allowedValue) { $success = true; break; } $printableAllowedValues[] = $allowedValue; } if (!$success) { $message = sprintf( 'The option "%s" with value %s is invalid.', $option, $this->formatValue($value) ); if (\count($printableAllowedValues) > 0) { $message .= sprintf( ' Accepted values are: %s.', $this->formatValues($printableAllowedValues) ); } throw new InvalidOptionsException($message); } } if (isset($this->normalizers[$option])) { if (isset($this->calling[$option])) { throw new OptionDefinitionException(sprintf('The options "%s" have a cyclic dependency.', implode('", "', array_keys($this->calling)))); } $normalizer = $this->normalizers[$option]; $this->calling[$option] = true; try { $value = $normalizer($this, $value); } finally { unset($this->calling[$option]); } } $this->resolved[$option] = $value; return $value; } private function verifyTypes($type, $value, array &$invalidTypes) { if (\is_array($value) && '[]' === substr($type, -2)) { return $this->verifyArrayType($type, $value, $invalidTypes); } if (self::isValueValidType($type, $value)) { return true; } if (!$invalidTypes) { $invalidTypes[$this->formatTypeOf($value, null)] = true; } return false; } private function verifyArrayType($type, array $value, array &$invalidTypes, $level = 0) { $type = substr($type, 0, -2); if ('[]' === substr($type, -2)) { $success = true; foreach ($value as $item) { if (!\is_array($item)) { $invalidTypes[$this->formatTypeOf($item, null)] = true; $success = false; } elseif (!$this->verifyArrayType($type, $item, $invalidTypes, $level + 1)) { $success = false; } } return $success; } $valid = true; foreach ($value as $item) { if (!self::isValueValidType($type, $item)) { $invalidTypes[$this->formatTypeOf($item, $type)] = $value; $valid = false; } } return $valid; } public function offsetExists($option) { if (!$this->locked) { throw new AccessException('Array access is only supported within closures of lazy options and normalizers.'); } return \array_key_exists($option, $this->defaults); } public function offsetSet($option, $value) { throw new AccessException('Setting options via array access is not supported. Use setDefault() instead.'); } public function offsetUnset($option) { throw new AccessException('Removing options via array access is not supported. Use remove() instead.'); } public function count() { if (!$this->locked) { throw new AccessException('Counting is only supported within closures of lazy options and normalizers.'); } return \count($this->defaults); } private function formatTypeOf($value, $type) { $suffix = ''; if ('[]' === substr($type, -2)) { $suffix = '[]'; $type = substr($type, 0, -2); while ('[]' === substr($type, -2)) { $type = substr($type, 0, -2); $value = array_shift($value); if (!\is_array($value)) { break; } $suffix .= '[]'; } if (\is_array($value)) { $subTypes = []; foreach ($value as $val) { $subTypes[$this->formatTypeOf($val, null)] = true; } return implode('|', array_keys($subTypes)).$suffix; } } return (\is_object($value) ? \get_class($value) : \gettype($value)).$suffix; } private function formatValue($value) { if (\is_object($value)) { return \get_class($value); } if (\is_array($value)) { return 'array'; } if (\is_string($value)) { return '"'.$value.'"'; } if (\is_resource($value)) { return 'resource'; } if (null === $value) { return 'null'; } if (false === $value) { return 'false'; } if (true === $value) { return 'true'; } return (string) $value; } private function formatValues(array $values) { foreach ($values as $key => $value) { $values[$key] = $this->formatValue($value); } return implode(', ', $values); } private static function isValueValidType($type, $value) { return (\function_exists($isFunction = 'is_'.$type) && $isFunction($value)) || $value instanceof $type; } } get = \Closure::bind(function ($property, $option, $message) { if (!$this->isDefined($option)) { throw new UndefinedOptionsException(sprintf('The option "%s" does not exist.', $option)); } if (!\array_key_exists($option, $this->{$property})) { throw new NoConfigurationException($message); } return $this->{$property}[$option]; }, $optionsResolver, $optionsResolver); } public function getDefault($option) { return \call_user_func($this->get, 'defaults', $option, sprintf('No default value was set for the "%s" option.', $option)); } public function getLazyClosures($option) { return \call_user_func($this->get, 'lazy', $option, sprintf('No lazy closures were set for the "%s" option.', $option)); } public function getAllowedTypes($option) { return \call_user_func($this->get, 'allowedTypes', $option, sprintf('No allowed types were set for the "%s" option.', $option)); } public function getAllowedValues($option) { return \call_user_func($this->get, 'allowedValues', $option, sprintf('No allowed values were set for the "%s" option.', $option)); } public function getNormalizer($option) { return \call_user_func($this->get, 'normalizers', $option, sprintf('No normalizer was set for the "%s" option.', $option)); } } mkdir($lockPath); } if (!is_writable($lockPath)) { throw new IOException(sprintf('The directory "%s" is not writable.', $lockPath), 0, null, $lockPath); } $this->file = sprintf('%s/sf.%s.%s.lock', $lockPath, preg_replace('/[^a-z0-9\._-]+/i', '-', $name), hash('sha256', $name)); } public function lock($blocking = false) { if ($this->handle) { return true; } $error = null; set_error_handler(function ($errno, $msg) use (&$error) { $error = $msg; }); if (!$this->handle = fopen($this->file, 'r+') ?: fopen($this->file, 'r')) { if ($this->handle = fopen($this->file, 'x')) { chmod($this->file, 0666); } elseif (!$this->handle = fopen($this->file, 'r+') ?: fopen($this->file, 'r')) { usleep(100); $this->handle = fopen($this->file, 'r+') ?: fopen($this->file, 'r'); } } restore_error_handler(); if (!$this->handle) { throw new IOException($error, 0, null, $this->file); } if (!flock($this->handle, LOCK_EX | ($blocking ? 0 : LOCK_NB))) { fclose($this->handle); $this->handle = null; return false; } return true; } public function release() { if ($this->handle) { flock($this->handle, LOCK_UN | LOCK_NB); fclose($this->handle); $this->handle = null; } } } mkdir(\dirname($targetFile)); $doCopy = true; if (!$overwriteNewerFiles && null === parse_url($originFile, PHP_URL_HOST) && is_file($targetFile)) { $doCopy = filemtime($originFile) > filemtime($targetFile); } if ($doCopy) { 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); } 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) { @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); } } } } public function mkdir($dirs, $mode = 0777) { foreach ($this->toIterable($dirs) as $dir) { if (is_dir($dir)) { continue; } if (!self::box('mkdir', $dir, $mode, true)) { if (!is_dir($dir)) { if (self::$lastError) { throw new IOException(sprintf('Failed to create "%s": %s.', $dir, self::$lastError), 0, null, $dir); } throw new IOException(sprintf('Failed to create "%s".', $dir), 0, null, $dir); } } } } 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; } public function touch($files, $time = null, $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); } } } 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)) { if (!(self::box('unlink', $file) || '\\' !== \DIRECTORY_SEPARATOR || self::box('rmdir', $file)) && file_exists($file)) { throw new IOException(sprintf('Failed to remove symlink "%s": %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": %s.', $file, self::$lastError)); } } elseif (!self::box('unlink', $file) && file_exists($file)) { throw new IOException(sprintf('Failed to remove file "%s": %s.', $file, self::$lastError)); } } } public function chmod($files, $mode, $umask = 0000, $recursive = false) { foreach ($this->toIterable($files) as $file) { if (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); } } } public function chown($files, $user, $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); } } } } public function chgrp($files, $group, $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) || (\defined('HHVM_VERSION') && !posix_getgrnam($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); } } } } public function rename($origin, $target, $overwrite = false) { 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)) { $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); } } private function isReadable($filename) { $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); } public function symlink($originDir, $targetDir, $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'); } } public function hardlink($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'); } } } private function linkException($origin, $target, $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); } public function readlink($path, $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); } public function makePathRelative($endPath, $startPath) { if (!$this->isAbsolutePath($endPath) || !$this->isAbsolutePath($startPath)) { @trigger_error(sprintf('Support for passing relative paths to %s() is deprecated since Symfony 3.4 and will be removed in 4.0.', __METHOD__), E_USER_DEPRECATED); } if ('\\' === \DIRECTORY_SEPARATOR) { $endPath = str_replace('\\', '/', $endPath); $startPath = str_replace('\\', '/', $startPath); } $stripDriveLetter = function ($path) { if (\strlen($path) > 2 && ':' === $path[1] && '/' === $path[2] && ctype_alpha($path[0])) { return substr($path, 2); } return $path; }; $endPath = $stripDriveLetter($endPath); $startPath = $stripDriveLetter($startPath); $startPathArr = explode('/', trim($startPath, '/')); $endPathArr = explode('/', trim($endPath, '/')); $normalizePathArray = function ($pathSegments, $absolute) { $result = []; foreach ($pathSegments as $segment) { if ('..' === $segment && ($absolute || \count($result))) { array_pop($result); } elseif ('.' !== $segment) { $result[] = $segment; } } return $result; }; $startPathArr = $normalizePathArray($startPathArr, static::isAbsolutePath($startPath)); $endPathArr = $normalizePathArray($endPathArr, static::isAbsolutePath($endPath)); $index = 0; while (isset($startPathArr[$index]) && isset($endPathArr[$index]) && $startPathArr[$index] === $endPathArr[$index]) { ++$index; } if (1 === \count($startPathArr) && '' === $startPathArr[0]) { $depth = 0; } else { $depth = \count($startPathArr) - $index; } $traverser = str_repeat('../', $depth); $endPathRemainder = implode('/', \array_slice($endPathArr, $index)); $relativePath = $traverser.('' !== $endPathRemainder ? $endPathRemainder.'/' : ''); return '' === $relativePath ? './' : $relativePath; } public function mirror($originDir, $targetDir, \Traversable $iterator = null, $options = []) { $targetDir = rtrim($targetDir, '/\\'); $originDir = rtrim($originDir, '/\\'); $originDirLen = \strlen($originDir); 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 = false; if (isset($options['copy_on_windows'])) { $copyOnWindows = $options['copy_on_windows']; } if (null === $iterator) { $flags = $copyOnWindows ? \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS : \FilesystemIterator::SKIP_DOTS; $iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($originDir, $flags), \RecursiveIteratorIterator::SELF_FIRST); } if ($this->exists($originDir)) { $this->mkdir($targetDir); } foreach ($iterator as $file) { $target = $targetDir.substr($file->getPathname(), $originDirLen); if ($copyOnWindows) { if (is_file($file)) { $this->copy($file, $target, isset($options['override']) ? $options['override'] : false); } elseif (is_dir($file)) { $this->mkdir($target); } else { throw new IOException(sprintf('Unable to guess "%s" file type.', $file), 0, null, $file); } } else { if (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); } } } } public function isAbsolutePath($file) { return strspn($file, '/\\', 0, 1) || (\strlen($file) > 3 && ctype_alpha($file[0]) && ':' === $file[1] && strspn($file, '/\\', 2, 1) ) || null !== parse_url($file, PHP_URL_SCHEME) ; } public function tempnam($dir, $prefix) { list($scheme, $hierarchy) = $this->getSchemeAndHierarchy($dir); if (null === $scheme || 'file' === $scheme || 'gs' === $scheme) { $tmpFile = @tempnam($hierarchy, $prefix); if (false !== $tmpFile) { if (null !== $scheme && 'gs' !== $scheme) { return $scheme.'://'.$tmpFile; } return $tmpFile; } throw new IOException('A temporary file could not be created.'); } for ($i = 0; $i < 10; ++$i) { $tmpFile = $dir.'/'.$prefix.uniqid(mt_rand(), true); $handle = @fopen($tmpFile, 'x+'); if (false === $handle) { continue; } @fclose($handle); return $tmpFile; } throw new IOException('A temporary file could not be created.'); } public function dumpFile($filename, $content) { $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); } $tmpFile = $this->tempnam($dir, basename($filename)); 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); } public function appendToFile($filename, $content) { $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) { return \is_array($files) || $files instanceof \Traversable ? $files : [$files]; } private function getSchemeAndHierarchy($filename) { $components = explode('://', $filename, 2); return 2 === \count($components) ? [$components[0], $components[1]] : [null, $components[0]]; } private static function box($func) { self::$lastError = null; set_error_handler(__CLASS__.'::handleError'); try { $result = \call_user_func_array($func, \array_slice(\func_get_args(), 1)); restore_error_handler(); return $result; } catch (\Throwable $e) { } catch (\Exception $e) { } restore_error_handler(); throw $e; } public static function handleError($type, $msg) { self::$lastError = $msg; } } path = $path; parent::__construct($message, $code, $previous); } public function getPath() { return $this->path; } } container = $container; $class = static::class; if ($this instanceof \PHPUnit_Framework_MockObject_MockObject || $this instanceof MockObject || $this instanceof \Prophecy\Doubler\DoubleInterface) { $class = get_parent_class($class); } if (__CLASS__ !== $class) { @trigger_error(sprintf('The %s class is deprecated since Symfony 3.3 and will be removed in 4.0. Use EventDispatcher with closure factories instead.', __CLASS__), E_USER_DEPRECATED); } } public function addListenerService($eventName, $callback, $priority = 0) { @trigger_error(sprintf('The %s class is deprecated since Symfony 3.3 and will be removed in 4.0. Use EventDispatcher with closure factories instead.', __CLASS__), E_USER_DEPRECATED); if (!\is_array($callback) || 2 !== \count($callback)) { throw new \InvalidArgumentException('Expected an ["service", "method"] argument.'); } $this->listenerIds[$eventName][] = [$callback[0], $callback[1], $priority]; } public function removeListener($eventName, $listener) { $this->lazyLoad($eventName); if (isset($this->listenerIds[$eventName])) { foreach ($this->listenerIds[$eventName] as $i => list($serviceId, $method)) { $key = $serviceId.'.'.$method; if (isset($this->listeners[$eventName][$key]) && $listener === [$this->listeners[$eventName][$key], $method]) { unset($this->listeners[$eventName][$key]); if (empty($this->listeners[$eventName])) { unset($this->listeners[$eventName]); } unset($this->listenerIds[$eventName][$i]); if (empty($this->listenerIds[$eventName])) { unset($this->listenerIds[$eventName]); } } } } parent::removeListener($eventName, $listener); } public function hasListeners($eventName = null) { if (null === $eventName) { return $this->listenerIds || $this->listeners || parent::hasListeners(); } if (isset($this->listenerIds[$eventName])) { return true; } return parent::hasListeners($eventName); } public function getListeners($eventName = null) { if (null === $eventName) { foreach ($this->listenerIds as $serviceEventName => $args) { $this->lazyLoad($serviceEventName); } } else { $this->lazyLoad($eventName); } return parent::getListeners($eventName); } public function getListenerPriority($eventName, $listener) { $this->lazyLoad($eventName); return parent::getListenerPriority($eventName, $listener); } public function addSubscriberService($serviceId, $class) { @trigger_error(sprintf('The %s class is deprecated since Symfony 3.3 and will be removed in 4.0. Use EventDispatcher with closure factories instead.', __CLASS__), E_USER_DEPRECATED); foreach ($class::getSubscribedEvents() as $eventName => $params) { if (\is_string($params)) { $this->listenerIds[$eventName][] = [$serviceId, $params, 0]; } elseif (\is_string($params[0])) { $this->listenerIds[$eventName][] = [$serviceId, $params[0], isset($params[1]) ? $params[1] : 0]; } else { foreach ($params as $listener) { $this->listenerIds[$eventName][] = [$serviceId, $listener[0], isset($listener[1]) ? $listener[1] : 0]; } } } } public function getContainer() { @trigger_error('The '.__METHOD__.'() method is deprecated since Symfony 3.3 as its class will be removed in 4.0. Inject the container or the services you need in your listeners/subscribers instead.', E_USER_DEPRECATED); return $this->container; } protected function lazyLoad($eventName) { if (isset($this->listenerIds[$eventName])) { foreach ($this->listenerIds[$eventName] as list($serviceId, $method, $priority)) { $listener = $this->container->get($serviceId); $key = $serviceId.'.'.$method; if (!isset($this->listeners[$eventName][$key])) { $this->addListener($eventName, [$listener, $method], $priority); } elseif ($this->listeners[$eventName][$key] !== $listener) { parent::removeListener($eventName, [$this->listeners[$eventName][$key], $method]); $this->addListener($eventName, [$listener, $method], $priority); } $this->listeners[$eventName][$key] = $listener; } } } } dispatcher = $dispatcher; $this->stopwatch = $stopwatch; $this->logger = $logger; $this->wrappedListeners = []; } public function addListener($eventName, $listener, $priority = 0) { $this->dispatcher->addListener($eventName, $listener, $priority); } public function addSubscriber(EventSubscriberInterface $subscriber) { $this->dispatcher->addSubscriber($subscriber); } public function removeListener($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); } public function removeSubscriber(EventSubscriberInterface $subscriber) { return $this->dispatcher->removeSubscriber($subscriber); } public function getListeners($eventName = null) { return $this->dispatcher->getListeners($eventName); } public function getListenerPriority($eventName, $listener) { 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); } public function hasListeners($eventName = null) { return $this->dispatcher->hasListeners($eventName); } public function dispatch($eventName, Event $event = null) { if (null === $this->callStack) { $this->callStack = new \SplObjectStorage(); } if (null === $event) { $event = new Event(); } if (null !== $this->logger && $event->isPropagationStopped()) { $this->logger->debug(sprintf('The "%s" event is already stopped. No listeners have been called.', $eventName)); } $this->preProcess($eventName); try { $this->preDispatch($eventName, $event); try { $e = $this->stopwatch->start($eventName, 'section'); try { $this->dispatcher->dispatch($eventName, $event); } finally { if ($e->isStarted()) { $e->stop(); } } } finally { $this->postDispatch($eventName, $event); } } finally { $this->postProcess($eventName); } return $event; } public function getCalledListeners() { if (null === $this->callStack) { return []; } $called = []; foreach ($this->callStack as $listener) { list($eventName) = $this->callStack->getInfo(); $called[] = $listener->getInfo($eventName); } return $called; } public function getNotCalledListeners() { 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]); } return []; } $calledListeners = []; if (null !== $this->callStack) { foreach ($this->callStack as $calledListener) { $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 reset() { $this->callStack = null; } public function __call($method, $arguments) { return \call_user_func_array([$this->dispatcher, $method], $arguments); } protected function preDispatch($eventName, Event $event) { } protected function postDispatch($eventName, Event $event) { } private function preProcess($eventName) { 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]); } } private function postProcess($eventName) { unset($this->wrappedListeners[$eventName]); $skipped = false; foreach ($this->dispatcher->getListeners($eventName) as $listener) { if (!$listener instanceof WrappedListener) { continue; } $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; } } listener = $listener; $this->stopwatch = $stopwatch; $this->dispatcher = $dispatcher; $this->called = false; $this->stoppedPropagation = false; if (\is_array($listener)) { $this->name = \is_object($listener[0]) ? \get_class($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_class($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() { return $this->called; } public function stoppedPropagation() { return $this->stoppedPropagation; } public function getPretty() { return $this->pretty; } public function getInfo($eventName) { 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(Event $event, $eventName, EventDispatcherInterface $dispatcher) { $dispatcher = $this->dispatcher ?: $dispatcher; $this->called = true; $this->priority = $dispatcher->getListenerPriority($eventName, $this->listener); $e = $this->stopwatch->start($this->name, 'event_listener'); \call_user_func($this->listener, $event, $eventName, $dispatcher); if ($e->isStarted()) { $e->stop(); } if ($event->isPropagationStopped()) { $this->stoppedPropagation = true; } } } subject = $subject; $this->arguments = $arguments; } public function getSubject() { return $this->subject; } public function getArgument($key) { if ($this->hasArgument($key)) { return $this->arguments[$key]; } throw new \InvalidArgumentException(sprintf('Argument "%s" not found.', $key)); } public function setArgument($key, $value) { $this->arguments[$key] = $value; return $this; } public function getArguments() { return $this->arguments; } public function setArguments(array $args = []) { $this->arguments = $args; return $this; } public function hasArgument($key) { return \array_key_exists($key, $this->arguments); } public function offsetGet($key) { return $this->getArgument($key); } public function offsetSet($key, $value) { $this->setArgument($key, $value); } public function offsetUnset($key) { if ($this->hasArgument($key)) { unset($this->arguments[$key]); } } public function offsetExists($key) { return $this->hasArgument($key); } public function getIterator() { return new \ArrayIterator($this->arguments); } } dispatcherService = $dispatcherService; $this->listenerTag = $listenerTag; $this->subscriberTag = $subscriberTag; } public function setHotPathEvents(array $hotPathEvents, $tagName = 'container.hot_path') { $this->hotPathEvents = array_flip($hotPathEvents); $this->hotPathTagName = $tagName; return $this; } public function process(ContainerBuilder $container) { if (!$container->hasDefinition($this->dispatcherService) && !$container->hasAlias($this->dispatcherService)) { return; } $definition = $container->findDefinition($this->dispatcherService); foreach ($container->findTaggedServiceIds($this->listenerTag, true) as $id => $events) { foreach ($events as $event) { $priority = isset($event['priority']) ? $event['priority'] : 0; if (!isset($event['event'])) { throw new InvalidArgumentException(sprintf('Service "%s" must define the "event" attribute on "%s" tags.', $id, $this->listenerTag)); } 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']); } $definition->addMethodCall('addListener', [$event['event'], [new ServiceClosureArgument(new Reference($id)), $event['method']], $priority]); if (isset($this->hotPathEvents[$event['event']])) { $container->getDefinition($id)->addTag($this->hotPathTagName); } } } $extractingDispatcher = new ExtractingEventDispatcher(); foreach ($container->findTaggedServiceIds($this->subscriberTag, true) as $id => $attributes) { $def = $container->getDefinition($id); $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; ExtractingEventDispatcher::$subscriber = $class; $extractingDispatcher->addSubscriber($extractingDispatcher); foreach ($extractingDispatcher->listeners as $args) { $args[1] = [new ServiceClosureArgument(new Reference($id)), $args[1]]; $definition->addMethodCall('addListener', $args); if (isset($this->hotPathEvents[$args[0]])) { $container->getDefinition($id)->addTag($this->hotPathTagName); } } $extractingDispatcher->listeners = []; } } } class ExtractingEventDispatcher extends EventDispatcher implements EventSubscriberInterface { public $listeners = []; public static $subscriber; public function addListener($eventName, $listener, $priority = 0) { $this->listeners[] = [$eventName, $listener[1], $priority]; } public static function getSubscribedEvents() { $callback = [self::$subscriber, 'getSubscribedEvents']; return $callback(); } } propagationStopped; } public function stopPropagation() { $this->propagationStopped = true; } } dispatcher = $dispatcher; } public function dispatch($eventName, Event $event = null) { return $this->dispatcher->dispatch($eventName, $event); } public function addListener($eventName, $listener, $priority = 0) { throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.'); } public function addSubscriber(EventSubscriberInterface $subscriber) { throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.'); } public function removeListener($eventName, $listener) { throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.'); } public function removeSubscriber(EventSubscriberInterface $subscriber) { throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.'); } public function getListeners($eventName = null) { return $this->dispatcher->getListeners($eventName); } public function getListenerPriority($eventName, $listener) { return $this->dispatcher->getListenerPriority($eventName, $listener); } public function hasListeners($eventName = null) { return $this->dispatcher->hasListeners($eventName); } } getListeners($eventName)) { $this->doDispatch($listeners, $eventName, $event); } return $event; } public function getListeners($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); } public function getListenerPriority($eventName, $listener) { if (empty($this->listeners[$eventName])) { return null; } if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure) { $listener[0] = $listener[0](); } 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) { $v[0] = $v[0](); $this->listeners[$eventName][$priority][$k] = $v; } if ($v === $listener) { return $priority; } } } return null; } public function hasListeners($eventName = null) { if (null !== $eventName) { return !empty($this->listeners[$eventName]); } foreach ($this->listeners as $eventListeners) { if ($eventListeners) { return true; } } return false; } public function addListener($eventName, $listener, $priority = 0) { $this->listeners[$eventName][$priority][] = $listener; unset($this->sorted[$eventName]); } public function removeListener($eventName, $listener) { if (empty($this->listeners[$eventName])) { return; } if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure) { $listener[0] = $listener[0](); } 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) { $v[0] = $v[0](); } if ($v === $listener) { unset($listeners[$k], $this->sorted[$eventName]); } else { $listeners[$k] = $v; } } if ($listeners) { $this->listeners[$eventName][$priority] = $listeners; } else { unset($this->listeners[$eventName][$priority]); } } } 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); } } } } 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]]); } } } protected function doDispatch($listeners, $eventName, Event $event) { foreach ($listeners as $listener) { if ($event->isPropagationStopped()) { break; } \call_user_func($listener, $event, $eventName, $this); } } private function sortListeners($eventName) { krsort($this->listeners[$eventName]); $this->sorted[$eventName] = []; foreach ($this->listeners[$eventName] as $priority => $listeners) { foreach ($listeners as $k => $listener) { if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure) { $listener[0] = $listener[0](); $this->listeners[$eventName][$priority][$k] = $listener; } $this->sorted[$eventName][] = $listener; } } } } name = $name; $this->version = $version; $this->terminal = new Terminal(); $this->defaultCommand = 'list'; } public function setDispatcher(EventDispatcherInterface $dispatcher) { $this->dispatcher = $dispatcher; } public function setCommandLoader(CommandLoaderInterface $commandLoader) { $this->commandLoader = $commandLoader; } public function run(InputInterface $input = null, OutputInterface $output = null) { putenv('LINES='.$this->terminal->getHeight()); putenv('COLUMNS='.$this->terminal->getWidth()); if (null === $input) { $input = new ArgvInput(); } if (null === $output) { $output = new ConsoleOutput(); } $renderException = function ($e) use ($output) { if (!$e instanceof \Exception) { $e = class_exists(FatalThrowableError::class) ? new FatalThrowableError($e) : new \ErrorException($e->getMessage(), $e->getCode(), E_ERROR, $e->getFile(), $e->getLine()); } if ($output instanceof ConsoleOutputInterface) { $this->renderException($e, $output->getErrorOutput()); } else { $this->renderException($e, $output); } }; if ($phpHandler = set_exception_handler($renderException)) { restore_exception_handler(); if (!\is_array($phpHandler) || !$phpHandler[0] instanceof ErrorHandler) { $debugHandler = true; } elseif ($debugHandler = $phpHandler[0]->setExceptionHandler($renderException)) { $phpHandler[0]->setExceptionHandler($debugHandler); } } if (null !== $this->dispatcher && $this->dispatcher->hasListeners(ConsoleEvents::EXCEPTION)) { @trigger_error(sprintf('The "ConsoleEvents::EXCEPTION" event is deprecated since Symfony 3.3 and will be removed in 4.0. Listen to the "ConsoleEvents::ERROR" event instead.'), E_USER_DEPRECATED); } $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 (!$phpHandler) { if (set_exception_handler($renderException) === $renderException) { restore_exception_handler(); } restore_exception_handler(); } elseif (!$debugHandler) { $finalHandler = $phpHandler[0]->setExceptionHandler(null); if ($finalHandler !== $renderException) { $phpHandler[0]->setExceptionHandler($finalHandler); } } } if ($this->autoExit) { if ($exitCode > 255) { $exitCode = 255; } exit($exitCode); } return $exitCode; } public function doRun(InputInterface $input, OutputInterface $output) { if (true === $input->hasParameterOption(['--version', '-V'], true)) { $output->writeln($this->getLongVersion()); return 0; } try { $input->bind($this->getDefinition()); } catch (ExceptionInterface $e) { } $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 { $e = $this->runningCommand = null; $command = $this->find($name); } catch (\Exception $e) { } catch (\Throwable $e) { } if (null !== $e) { if (null !== $this->dispatcher) { $event = new ConsoleErrorEvent($input, $output, $e); $this->dispatcher->dispatch(ConsoleEvents::ERROR, $event); $e = $event->getError(); if (0 === $event->getExitCode()) { return 0; } } throw $e; } $this->runningCommand = $command; $exitCode = $this->doRunCommand($command, $input, $output); $this->runningCommand = null; return $exitCode; } public function setHelperSet(HelperSet $helperSet) { $this->helperSet = $helperSet; } public function getHelperSet() { if (!$this->helperSet) { $this->helperSet = $this->getDefaultHelperSet(); } return $this->helperSet; } public function setDefinition(InputDefinition $definition) { $this->definition = $definition; } public function getDefinition() { if (!$this->definition) { $this->definition = $this->getDefaultInputDefinition(); } if ($this->singleCommand) { $inputDefinition = $this->definition; $inputDefinition->setArguments(); return $inputDefinition; } return $this->definition; } public function getHelp() { return $this->getLongVersion(); } public function areExceptionsCaught() { return $this->catchExceptions; } public function setCatchExceptions($boolean) { $this->catchExceptions = (bool) $boolean; } public function isAutoExitEnabled() { return $this->autoExit; } public function setAutoExit($boolean) { $this->autoExit = (bool) $boolean; } public function getName() { return $this->name; } public function setName($name) { $this->name = $name; } public function getVersion() { return $this->version; } public function setVersion($version) { $this->version = $version; } public function getLongVersion() { if ('UNKNOWN' !== $this->getName()) { if ('UNKNOWN' !== $this->getVersion()) { return sprintf('%s %s', $this->getName(), $this->getVersion()); } return $this->getName(); } return 'Console Tool'; } public function register($name) { return $this->add(new Command($name)); } public function addCommands(array $commands) { foreach ($commands as $command) { $this->add($command); } } public function add(Command $command) { $this->init(); $command->setApplication($this); if (!$command->isEnabled()) { $command->setApplication(null); return null; } $command->getDefinition(); if (!$command->getName()) { throw new LogicException(sprintf('The command defined in "%s" cannot have an empty name.', \get_class($command))); } $this->commands[$command->getName()] = $command; foreach ($command->getAliases() as $alias) { $this->commands[$alias] = $command; } return $command; } public function get($name) { $this->init(); if (!$this->has($name)) { throw new CommandNotFoundException(sprintf('The command "%s" does not exist.', $name)); } $command = $this->commands[$name]; if ($this->wantHelps) { $this->wantHelps = false; $helpCommand = $this->get('help'); $helpCommand->setCommand($command); return $helpCommand; } return $command; } public function has($name) { $this->init(); return isset($this->commands[$name]) || ($this->commandLoader && $this->commandLoader->has($name) && $this->add($this->commandLoader->get($name))); } 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))); } public function findNamespace($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 CommandNotFoundException($message, $alternatives); } $exact = \in_array($namespace, $namespaces, true); if (\count($namespaces) > 1 && !$exact) { throw new CommandNotFoundException(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); } public function find($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 (empty($commands) || \count(preg_grep('{^'.$expr.'$}i', $commands)) < 1) { if (false !== $pos = strrpos($name, ':')) { $this->findNamespace(substr($name, 0, $pos)); } $message = sprintf('Command "%s" is not defined.', $name); if ($alternatives = $this->findAlternatives($name, $allCommands)) { $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)); } 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); })); } $exact = \in_array($name, $commands, true) || isset($aliases[$name]); if (\count($commands) > 1 && !$exact) { $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) { if ($commandList[$cmd]->isHidden()) { 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)); $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)); } return $this->get($exact ? $name : reset($commands)); } public function all($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; } public static function getAbbreviations($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 renderException(\Exception $e, OutputInterface $output) { $output->writeln('', OutputInterface::VERBOSITY_QUIET); $this->doRenderException($e, $output); if (null !== $this->runningCommand) { $output->writeln(sprintf('%s', sprintf($this->runningCommand->getSynopsis(), $this->getName())), OutputInterface::VERBOSITY_QUIET); $output->writeln('', OutputInterface::VERBOSITY_QUIET); } } protected function doRenderException(\Exception $e, OutputInterface $output) { do { $message = trim($e->getMessage()); if ('' === $message || OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) { $title = sprintf(' [%s%s] ', \get_class($e), 0 !== ($code = $e->getCode()) ? ' ('.$code.')' : ''); $len = Helper::strlen($title); } else { $len = 0; } $width = $this->terminal->getWidth() ? $this->terminal->getWidth() - 1 : PHP_INT_MAX; if (\defined('HHVM_VERSION') && $width > 1 << 31) { $width = 1 << 31; } $lines = []; foreach ('' !== $message ? preg_split('/\r?\n/', $message) : [] as $line) { foreach ($this->splitStringByWidth($line, $width - 4) as $line) { $lineLength = Helper::strlen($line) + 4; $lines[] = [$line, $lineLength]; $len = max($lineLength, $len); } } $messages = []; if (!$e instanceof ExceptionInterface || OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) { $messages[] = sprintf('%s', OutputFormatter::escape(sprintf('In %s line %s:', basename($e->getFile()) ?: 'n/a', $e->getLine() ?: 'n/a'))); } $messages[] = $emptyLine = sprintf('%s', str_repeat(' ', $len)); if ('' === $message || OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) { $messages[] = sprintf('%s%s', $title, str_repeat(' ', max(0, $len - Helper::strlen($title)))); } foreach ($lines as $line) { $messages[] = sprintf(' %s %s', 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('Exception trace:', OutputInterface::VERBOSITY_QUIET); $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 %s:%s', $class, $function ? $type.$function.'()' : '', $file, $line), OutputInterface::VERBOSITY_QUIET); } $output->writeln('', OutputInterface::VERBOSITY_QUIET); } } while ($e = $e->getPrevious()); } protected function getTerminalWidth() { @trigger_error(sprintf('The "%s()" method is deprecated as of 3.2 and will be removed in 4.0. Create a Terminal instance instead.', __METHOD__), E_USER_DEPRECATED); return $this->terminal->getWidth(); } protected function getTerminalHeight() { @trigger_error(sprintf('The "%s()" method is deprecated as of 3.2 and will be removed in 4.0. Create a Terminal instance instead.', __METHOD__), E_USER_DEPRECATED); return $this->terminal->getHeight(); } public function getTerminalDimensions() { @trigger_error(sprintf('The "%s()" method is deprecated as of 3.2 and will be removed in 4.0. Create a Terminal instance instead.', __METHOD__), E_USER_DEPRECATED); return [$this->terminal->getWidth(), $this->terminal->getHeight()]; } public function setTerminalDimensions($width, $height) { @trigger_error(sprintf('The "%s()" method is deprecated as of 3.2 and will be removed in 4.0. Set the COLUMNS and LINES env vars instead.', __METHOD__), E_USER_DEPRECATED); putenv('COLUMNS='.$width); putenv('LINES='.$height); return $this; } 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); } elseif (\function_exists('posix_isatty')) { $inputStream = null; if ($input instanceof StreamableInputInterface) { $inputStream = $input->getStream(); } if (!$inputStream && $this->getHelperSet()->has('question')) { $inputStream = $this->getHelperSet()->get('question')->getInputStream(false); } if (!@posix_isatty($inputStream) && false === getenv('SHELL_INTERACTIVE')) { $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); } putenv('SHELL_VERBOSITY='.$shellVerbosity); $_ENV['SHELL_VERBOSITY'] = $shellVerbosity; $_SERVER['SHELL_VERBOSITY'] = $shellVerbosity; } protected function doRunCommand(Command $command, InputInterface $input, OutputInterface $output) { foreach ($command->getHelperSet() as $helper) { if ($helper instanceof InputAwareInterface) { $helper->setInput($input); } } if (null === $this->dispatcher) { return $command->run($input, $output); } try { $command->mergeApplicationDefinition(); $input->bind($command->getDefinition()); } catch (ExceptionInterface $e) { } $event = new ConsoleCommandEvent($command, $input, $output); $e = null; try { $this->dispatcher->dispatch(ConsoleEvents::COMMAND, $event); if ($event->commandShouldRun()) { $exitCode = $command->run($input, $output); } else { $exitCode = ConsoleCommandEvent::RETURN_CODE_DISABLED; } } catch (\Exception $e) { } catch (\Throwable $e) { } if (null !== $e) { if ($this->dispatcher->hasListeners(ConsoleEvents::EXCEPTION)) { $x = $e instanceof \Exception ? $e : new FatalThrowableError($e); $event = new ConsoleExceptionEvent($command, $input, $output, $x, $x->getCode()); $this->dispatcher->dispatch(ConsoleEvents::EXCEPTION, $event); if ($x !== $event->getException()) { $e = $event->getException(); } } $event = new ConsoleErrorEvent($input, $output, $e, $command); $this->dispatcher->dispatch(ConsoleEvents::ERROR, $event); $e = $event->getError(); if (0 === $exitCode = $event->getExitCode()) { $e = null; } } $event = new ConsoleTerminateEvent($command, $input, $output, $exitCode); $this->dispatcher->dispatch(ConsoleEvents::TERMINATE, $event); if (null !== $e) { throw $e; } return $event->getExitCode(); } protected function getCommandName(InputInterface $input) { return $this->singleCommand ? $this->defaultCommand : $input->getFirstArgument(); } protected function getDefaultInputDefinition() { return new InputDefinition([ new InputArgument('command', InputArgument::REQUIRED, 'The command to execute'), new InputOption('--help', '-h', InputOption::VALUE_NONE, 'Display this help message'), 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'), ]); } protected function getDefaultCommands() { return [new HelpCommand(), new ListCommand()]; } protected function getDefaultHelperSet() { return new HelperSet([ new FormatterHelper(), new DebugFormatterHelper(), new ProcessHelper(), new QuestionHelper(), ]); } private function getAbbreviationSuggestions($abbrevs) { return ' '.implode("\n ", $abbrevs); } public function extractNamespace($name, $limit = null) { $parts = explode(':', $name, -1); return implode(':', null === $limit ? $parts : \array_slice($parts, 0, $limit)); } private function findAlternatives($name, $collection) { $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); } public function setDefaultCommand($commandName, $isSingleCommand = false) { $this->defaultCommand = $commandName; if ($isSingleCommand) { $this->find($commandName); $this->singleCommand = true; } return $this; } public function isSingleCommand() { return $this->singleCommand; } private function splitStringByWidth($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 = ''; foreach (preg_split('//u', $utf8String) as $char) { if (mb_strwidth($line.$char, 'utf8') <= $width) { $line .= $char; continue; } $lines[] = str_pad($line, $width); $line = $char; } $lines[] = \count($lines) ? str_pad($line, $width) : $line; mb_convert_variables($encoding, 'utf8', $lines); return $lines; } private function extractAllNamespaces($name) { $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); } } } 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; } 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; if (self::ERROR === $this->formatLevelMap[$level]) { if ($this->output instanceof ConsoleOutputInterface) { $output = $output->getErrorOutput(); } $this->errored = true; } if ($output->getVerbosity() >= $this->verbosityLevelMap[$level]) { $output->writeln(sprintf('<%1$s>[%2$s] %3$s', $this->formatLevelMap[$level], $level, $this->interpolate($message, $context)), $this->verbosityLevelMap[$level]); } } public function hasErrored() { return $this->errored; } private function interpolate($message, array $context) { 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); } } openOutputStream(), $verbosity, $decorated, $formatter); $actualDecorated = $this->isDecorated(); $this->stderr = new StreamOutput($this->openErrorStream(), $verbosity, $decorated, $this->getFormatter()); if (null === $decorated) { $this->setDecorated($actualDecorated && $this->stderr->isDecorated()); } } public function setDecorated($decorated) { parent::setDecorated($decorated); $this->stderr->setDecorated($decorated); } public function setFormatter(OutputFormatterInterface $formatter) { parent::setFormatter($formatter); $this->stderr->setFormatter($formatter); } public function setVerbosity($level) { parent::setVerbosity($level); $this->stderr->setVerbosity($level); } public function getErrorOutput() { return $this->stderr; } public function setErrorOutput(OutputInterface $error) { $this->stderr = $error; } protected function hasStdoutSupport() { return false === $this->isRunningOS400(); } protected function hasStderrSupport() { return false === $this->isRunningOS400(); } private function isRunningOS400() { $checks = [ \function_exists('php_uname') ? php_uname('s') : '', getenv('OSTYPE'), PHP_OS, ]; return false !== stripos(implode(';', $checks), 'OS400'); } private function openOutputStream() { if (!$this->hasStdoutSupport()) { return fopen('php://output', 'w'); } return @fopen('php://stdout', 'w') ?: fopen('php://output', 'w'); } private function openErrorStream() { return fopen($this->hasStderrSupport() ? 'php://stderr' : 'php://output', 'w'); } } verbosity = null === $verbosity ? self::VERBOSITY_NORMAL : $verbosity; $this->formatter = $formatter ?: new OutputFormatter(); $this->formatter->setDecorated($decorated); } public function setFormatter(OutputFormatterInterface $formatter) { $this->formatter = $formatter; } public function getFormatter() { return $this->formatter; } public function setDecorated($decorated) { $this->formatter->setDecorated($decorated); } public function isDecorated() { return $this->formatter->isDecorated(); } public function setVerbosity($level) { $this->verbosity = (int) $level; } public function getVerbosity() { return $this->verbosity; } public function isQuiet() { return self::VERBOSITY_QUIET === $this->verbosity; } public function isVerbose() { return self::VERBOSITY_VERBOSE <= $this->verbosity; } public function isVeryVerbose() { return self::VERBOSITY_VERY_VERBOSE <= $this->verbosity; } public function isDebug() { return self::VERBOSITY_DEBUG <= $this->verbosity; } public function writeln($messages, $options = self::OUTPUT_NORMAL) { $this->write($messages, true, $options); } public function write($messages, $newline = false, $options = self::OUTPUT_NORMAL) { $messages = (array) $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); } } abstract protected function doWrite($message, $newline); } stream = $stream; if (null === $decorated) { $decorated = $this->hasColorSupport(); } parent::__construct($verbosity, $decorated, $formatter); } public function getStream() { return $this->stream; } protected function doWrite($message, $newline) { if ($newline) { $message .= PHP_EOL; } @fwrite($this->stream, $message); fflush($this->stream); } protected function hasColorSupport() { 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'); } if (\function_exists('stream_isatty')) { return @stream_isatty($this->stream); } if (\function_exists('posix_isatty')) { return @posix_isatty($this->stream); } $stat = @fstat($this->stream); return $stat ? 0020000 === ($stat['mode'] & 0170000) : false; } } buffer; $this->buffer = ''; return $content; } protected function doWrite($message, $newline) { $this->buffer .= $message; if ($newline) { $this->buffer .= PHP_EOL; } } } started[$id] = ['border' => ++$this->count % \count($this->colors)]; return sprintf("%s %s %s\n", $this->getBorder($id), $prefix, $message); } public function progress($id, $buffer, $error = false, $prefix = 'OUT', $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 %s ', $this->getBorder($id), $errorPrefix); $this->started[$id]['err'] = true; } $message .= str_replace("\n", sprintf("\n%s %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 %s ', $this->getBorder($id), $prefix); $this->started[$id]['out'] = true; } $message .= str_replace("\n", sprintf("\n%s %s ", $this->getBorder($id), $prefix), $buffer); } return $message; } public function stop($id, $message, $successful, $prefix = 'RES') { $trailingEOL = isset($this->started[$id]['out']) || isset($this->started[$id]['err']) ? "\n" : ''; if ($successful) { return sprintf("%s%s %s %s\n", $trailingEOL, $this->getBorder($id), $prefix, $message); } $message = sprintf("%s%s %s %s\n", $trailingEOL, $this->getBorder($id), $prefix, $message); unset($this->started[$id]['out'], $this->started[$id]['err']); return $message; } private function getBorder($id) { return sprintf(' ', $this->colors[$this->started[$id]['border']]); } public function getName() { return 'debug_formatter'; } } getValidator(); $question->setValidator(function ($value) use ($validator) { if (null !== $validator) { $value = $validator($value); } else { if (!\is_array($value) && !\is_bool($value) && 0 === \strlen($value)) { @trigger_error('The default question validator is deprecated since Symfony 3.3 and will not be used anymore in version 4.0. Set a custom question validator if needed.', E_USER_DEPRECATED); throw new LogicException('A value is required.'); } } return $value; }); return parent::ask($input, $output, $question); } protected function writePrompt(OutputInterface $output, Question $question) { $text = OutputFormatter::escapeTrailingBackslash($question->getQuestion()); $default = $question->getDefault(); switch (true) { case null === $default: $text = sprintf(' %s:', $text); break; case $question instanceof ConfirmationQuestion: $text = sprintf(' %s (yes/no) [%s]:', $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(' %s [%s]:', $text, OutputFormatter::escape(implode(', ', $default))); break; case $question instanceof ChoiceQuestion: $choices = $question->getChoices(); $text = sprintf(' %s [%s]:', $text, OutputFormatter::escape(isset($choices[$default]) ? $choices[$default] : $default)); break; default: $text = sprintf(' %s [%s]:', $text, OutputFormatter::escape($default)); } $output->writeln($text); $prompt = ' > '; if ($question instanceof ChoiceQuestion) { $output->writeln($this->formatChoiceQuestionChoices($question, 'comment')); $prompt = $question->getPrompt(); } $output->write($prompt); } protected function writeError(OutputInterface $output, \Exception $error) { if ($output instanceof SymfonyStyle) { $output->newLine(); $output->error($error->getMessage()); return; } parent::writeError($output, $error); } } output = $output; if (!self::$styles) { self::$styles = self::initStyles(); } $this->setStyle('default'); } public static function setStyleDefinition($name, TableStyle $style) { if (!self::$styles) { self::$styles = self::initStyles(); } self::$styles[$name] = $style; } public static function getStyleDefinition($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)); } public function setStyle($name) { $this->style = $this->resolveStyle($name); return $this; } public function getStyle() { return $this->style; } public function setColumnStyle($columnIndex, $name) { $columnIndex = (int) $columnIndex; $this->columnStyles[$columnIndex] = $this->resolveStyle($name); return $this; } public function getColumnStyle($columnIndex) { if (isset($this->columnStyles[$columnIndex])) { return $this->columnStyles[$columnIndex]; } return $this->getStyle(); } public function setColumnWidth($columnIndex, $width) { $this->columnWidths[(int) $columnIndex] = (int) $width; return $this; } public function setColumnWidths(array $widths) { $this->columnWidths = []; foreach ($widths as $index => $width) { $this->setColumnWidth($index, $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; } public function setRow($column, array $row) { $this->rows[$column] = $row; return $this; } public function render() { $this->calculateNumberOfColumns(); $rows = $this->buildTableRows($this->rows); $headers = $this->buildTableRows($this->headers); $this->calculateColumnsWidth(array_merge($headers, $rows)); $this->renderRowSeparator(); if (!empty($headers)) { foreach ($headers as $header) { $this->renderRow($header, $this->style->getCellHeaderFormat()); $this->renderRowSeparator(); } } foreach ($rows as $row) { if ($row instanceof TableSeparator) { $this->renderRowSeparator(); } else { $this->renderRow($row, $this->style->getCellRowFormat()); } } if (!empty($rows)) { $this->renderRowSeparator(); } $this->cleanup(); } private function renderRowSeparator() { if (0 === $count = $this->numberOfColumns) { return; } if (!$this->style->getHorizontalBorderChar() && !$this->style->getCrossingChar()) { return; } $markup = $this->style->getCrossingChar(); for ($column = 0; $column < $count; ++$column) { $markup .= str_repeat($this->style->getHorizontalBorderChar(), $this->effectiveColumnWidths[$column]).$this->style->getCrossingChar(); } $this->output->writeln(sprintf($this->style->getBorderFormat(), $markup)); } private function renderColumnSeparator() { return sprintf($this->style->getBorderFormat(), $this->style->getVerticalBorderChar()); } private function renderRow(array $row, $cellFormat) { if (empty($row)) { return; } $rowContent = $this->renderColumnSeparator(); foreach ($this->getRowColumns($row) as $column) { $rowContent .= $this->renderCell($row, $column, $cellFormat); $rowContent .= $this->renderColumnSeparator(); } $this->output->writeln($rowContent); } private function renderCell(array $row, $column, $cellFormat) { $cell = isset($row[$column]) ? $row[$column] : ''; $width = $this->effectiveColumnWidths[$column]; if ($cell instanceof TableCell && $cell->getColspan() > 1) { foreach (range($column + 1, $column + $cell->getColspan() - 1) as $nextColumn) { $width += $this->getColumnSeparatorWidth() + $this->effectiveColumnWidths[$nextColumn]; } } 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->getHorizontalBorderChar(), $width)); } $width += Helper::strlen($cell) - Helper::strlenWithoutDecoration($this->output->getFormatter(), $cell); $content = sprintf($style->getCellRowContentFormat(), $cell); return sprintf($cellFormat, str_pad($content, $width, $style->getPaddingChar(), $style->getPadType())); } private function calculateNumberOfColumns() { if (null !== $this->numberOfColumns) { return; } $columns = [0]; foreach (array_merge($this->headers, $this->rows) as $row) { if ($row instanceof TableSeparator) { continue; } $columns[] = $this->getNumberOfColumns($row); } $this->numberOfColumns = max($columns); } private function buildTableRows($rows) { $unmergedRows = []; for ($rowKey = 0; $rowKey < \count($rows); ++$rowKey) { $rows = $this->fillNextRows($rows, $rowKey); foreach ($rows[$rowKey] as $column => $cell) { if (!strstr($cell, "\n")) { continue; } $lines = explode("\n", str_replace("\n", "\n", $cell)); foreach ($lines as $lineKey => $line) { if ($cell instanceof TableCell) { $line = new TableCell($line, ['colspan' => $cell->getColspan()]); } if (0 === $lineKey) { $rows[$rowKey][$column] = $line; } else { $unmergedRows[$rowKey][$lineKey][$column] = $line; } } } } $tableRows = []; foreach ($rows as $rowKey => $row) { $tableRows[] = $this->fillCells($row); if (isset($unmergedRows[$rowKey])) { $tableRows = array_merge($tableRows, $unmergedRows[$rowKey]); } } return $tableRows; } private function fillNextRows(array $rows, $line) { $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.', \gettype($cell))); } if ($cell instanceof TableCell && $cell->getRowspan() > 1) { $nbLines = $cell->getRowspan() - 1; $lines = [$cell]; if (strstr($cell, "\n")) { $lines = explode("\n", str_replace("\n", "\n", $cell)); $nbLines = \count($lines) > $nbLines ? substr_count($cell, "\n") : $nbLines; $rows[$line][$column] = new TableCell($lines[0], ['colspan' => $cell->getColspan()]); unset($lines[0]); } $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()]); if ($nbLines === $unmergedRowKey - $line) { break; } } } } foreach ($unmergedRows as $unmergedRowKey => $unmergedRow) { if (isset($rows[$unmergedRowKey]) && \is_array($rows[$unmergedRowKey]) && ($this->getNumberOfColumns($rows[$unmergedRowKey]) + $this->getNumberOfColumns($unmergedRows[$unmergedRowKey]) <= $this->numberOfColumns)) { foreach ($unmergedRow as $cellKey => $cell) { 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; } 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) { $newRow[] = ''; } } } return $newRow ?: $row; } private function copyRow(array $rows, $line) { $row = $rows[$line]; foreach ($row as $cellKey => $cellValue) { $row[$cellKey] = ''; if ($cellValue instanceof TableCell) { $row[$cellKey] = new TableCell('', ['colspan' => $cellValue->getColspan()]); } } return $row; } private function getNumberOfColumns(array $row) { $columns = \count($row); foreach ($row as $column) { $columns += $column instanceof TableCell ? ($column->getColspan() - 1) : 0; } return $columns; } private function getRowColumns(array $row) { $columns = range(0, $this->numberOfColumns - 1); foreach ($row as $cellKey => $cell) { if ($cell instanceof TableCell && $cell->getColspan() > 1) { $columns = array_diff($columns, range($cellKey + 1, $cellKey + $cell->getColspan() - 1)); } } return $columns; } private function calculateColumnsWidth(array $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() { return Helper::strlen(sprintf($this->style->getBorderFormat(), $this->style->getVerticalBorderChar())); } private function getCellWidth(array $row, $column) { $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; return max($cellWidth, $columnWidth); } private function cleanup() { $this->effectiveColumnWidths = []; $this->numberOfColumns = null; } private static function initStyles() { $borderless = new TableStyle(); $borderless ->setHorizontalBorderChar('=') ->setVerticalBorderChar(' ') ->setCrossingChar(' ') ; $compact = new TableStyle(); $compact ->setHorizontalBorderChar('') ->setVerticalBorderChar(' ') ->setCrossingChar('') ->setCellRowContentFormat('%s') ; $styleGuide = new TableStyle(); $styleGuide ->setHorizontalBorderChar('-') ->setVerticalBorderChar(' ') ->setCrossingChar(' ') ->setCellHeaderFormat('%s') ; return [ 'default' => new TableStyle(), 'borderless' => $borderless, 'compact' => $compact, 'symfony-style-guide' => $styleGuide, ]; } private function resolveStyle($name) { 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)); } } register('txt', new TextDescriptor()) ->register('xml', new XmlDescriptor()) ->register('json', new JsonDescriptor()) ->register('md', new MarkdownDescriptor()) ; } public function describe(OutputInterface $output, $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); } public function register($format, DescriptorInterface $descriptor) { $this->descriptors[$format] = $descriptor; return $this; } public function getName() { return 'descriptor'; } } %s'; private $cellRowFormat = '%s'; private $cellRowContentFormat = ' %s '; private $borderFormat = '%s'; private $padType = STR_PAD_RIGHT; public function setPaddingChar($paddingChar) { if (!$paddingChar) { throw new LogicException('The padding char must not be empty.'); } $this->paddingChar = $paddingChar; return $this; } public function getPaddingChar() { return $this->paddingChar; } public function setHorizontalBorderChar($horizontalBorderChar) { $this->horizontalBorderChar = $horizontalBorderChar; return $this; } public function getHorizontalBorderChar() { return $this->horizontalBorderChar; } public function setVerticalBorderChar($verticalBorderChar) { $this->verticalBorderChar = $verticalBorderChar; return $this; } public function getVerticalBorderChar() { return $this->verticalBorderChar; } public function setCrossingChar($crossingChar) { $this->crossingChar = $crossingChar; return $this; } public function getCrossingChar() { return $this->crossingChar; } public function setCellHeaderFormat($cellHeaderFormat) { $this->cellHeaderFormat = $cellHeaderFormat; return $this; } public function getCellHeaderFormat() { return $this->cellHeaderFormat; } public function setCellRowFormat($cellRowFormat) { $this->cellRowFormat = $cellRowFormat; return $this; } public function getCellRowFormat() { return $this->cellRowFormat; } public function setCellRowContentFormat($cellRowContentFormat) { $this->cellRowContentFormat = $cellRowContentFormat; return $this; } public function getCellRowContentFormat() { return $this->cellRowContentFormat; } public function setBorderFormat($borderFormat) { $this->borderFormat = $borderFormat; return $this; } public function getBorderFormat() { return $this->borderFormat; } public function setPadType($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; } public function getPadType() { return $this->padType; } } input = $input; } } [%s] %s', $style, $section, $style, $message); } public function formatBlock($messages, $style, $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', $style, $messages[$i], $style); } return implode("\n", $messages); } public function truncate($message, $length, $suffix = '...') { $computedLength = $length - self::strlen($suffix); if ($computedLength > self::strlen($message)) { return $message; } return self::substr($message, 0, $length).$suffix; } public function getName() { return 'formatter'; } } $helper) { $this->set($helper, \is_int($alias) ? null : $alias); } } public function set(HelperInterface $helper, $alias = null) { $this->helpers[$helper->getName()] = $helper; if (null !== $alias) { $this->helpers[$alias] = $helper; } $helper->setHelperSet($this); } public function has($name) { return isset($this->helpers[$name]); } public function get($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; } public function getCommand() { return $this->command; } public function getIterator() { return new \ArrayIterator($this->helpers); } } getErrorOutput(); } if (!$input->isInteractive()) { $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 = trim($v); $default[$k] = isset($choices[$v]) ? $choices[$v] : $v; } } return $default; } if ($input instanceof StreamableInputInterface && $stream = $input->getStream()) { $this->inputStream = $stream; } if (!$question->getValidator()) { return $this->doAsk($output, $question); } $interviewer = function () use ($output, $question) { return $this->doAsk($output, $question); }; return $this->validateAttempts($interviewer, $output, $question); } public function setInputStream($stream) { @trigger_error(sprintf('The %s() method is deprecated since Symfony 3.2 and will be removed in 4.0. Use %s::setStream() instead.', __METHOD__, StreamableInputInterface::class), E_USER_DEPRECATED); if (!\is_resource($stream)) { throw new InvalidArgumentException('Input stream must be a valid resource.'); } $this->inputStream = $stream; } public function getInputStream() { if (0 === \func_num_args() || func_get_arg(0)) { @trigger_error(sprintf('The %s() method is deprecated since Symfony 3.2 and will be removed in 4.0. Use %s::getStream() instead.', __METHOD__, StreamableInputInterface::class), E_USER_DEPRECATED); } return $this->inputStream; } public function getName() { return 'question'; } public static function disableStty() { self::$stty = false; } private function doAsk(OutputInterface $output, Question $question) { $this->writePrompt($output, $question); $inputStream = $this->inputStream ?: STDIN; $autocomplete = $question->getAutocompleterValues(); if (null === $autocomplete || !Terminal::hasSttyAvailable()) { $ret = false; if ($question->isHidden()) { try { $ret = trim($this->getHiddenResponse($output, $inputStream)); } catch (RuntimeException $e) { if (!$question->isHiddenFallback()) { throw $e; } } } if (false === $ret) { $ret = fgets($inputStream, 4096); if (false === $ret) { throw new RuntimeException('Aborted.'); } $ret = trim($ret); } } else { $ret = trim($this->autocomplete($output, $question, $inputStream, \is_array($autocomplete) ? $autocomplete : iterator_to_array($autocomplete, false))); } $ret = \strlen($ret) > 0 ? $ret : $question->getDefault(); if ($normalizer = $question->getNormalizer()) { return $normalizer($ret); } return $ret; } 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); } protected function formatChoiceQuestionChoices(ChoiceQuestion $question, $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] %s", $key, $value); } return $messages; } 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->getMessage().''; } $output->writeln($message); } private function autocomplete(OutputInterface $output, Question $question, $inputStream, array $autocomplete) { $fullChoice = ''; $ret = ''; $i = 0; $ofs = -1; $matches = $autocomplete; $numMatches = \count($matches); $sttyMode = shell_exec('stty -g'); shell_exec('stty -icanon -echo'); $output->getFormatter()->setStyle('hl', new OutputFormatterStyle('black', 'white')); while (!feof($inputStream)) { $c = fread($inputStream, 1); if (false === $c || ('' === $ret && '' === $c && null === $question->getDefault())) { shell_exec(sprintf('stty %s', $sttyMode)); throw new RuntimeException('Aborted.'); } elseif ("\177" === $c) { if (0 === $numMatches && 0 !== $i) { --$i; $fullChoice = self::substr($fullChoice, 0, $i); $output->write("\033[1D"); } if (0 === $i) { $ofs = -1; $matches = $autocomplete; $numMatches = \count($matches); } else { $numMatches = 0; } $ret = self::substr($ret, 0, $i); } elseif ("\033" === $c) { $c .= fread($inputStream, 2); 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 = $matches[$ofs]; $remainingCharacters = substr($ret, \strlen(trim($this->mostRecentlyEnteredValue($fullChoice)))); $output->write($remainingCharacters); $fullChoice .= $remainingCharacters; $i = self::strlen($fullChoice); } 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 as $value) { if (0 === strpos($value, $tempRet)) { $matches[$numMatches++] = $value; } } } $output->write("\033[K"); if ($numMatches > 0 && -1 !== $ofs) { $output->write("\0337"); $charactersEntered = \strlen(trim($this->mostRecentlyEnteredValue($fullChoice))); $output->write(''.OutputFormatter::escapeTrailingBackslash(substr($matches[$ofs], $charactersEntered)).''); $output->write("\0338"); } } shell_exec(sprintf('stty %s', $sttyMode)); return $fullChoice; } private function mostRecentlyEnteredValue($entered) { if (false === strpos($entered, ',')) { return $entered; } $choices = explode(',', $entered); if (\strlen($lastChoice = trim($choices[\count($choices) - 1])) > 0) { return $lastChoice; } return $entered; } private function getHiddenResponse(OutputInterface $output, $inputStream) { if ('\\' === \DIRECTORY_SEPARATOR) { $exe = __DIR__.'/../Resources/bin/hiddeninput.exe'; if ('phar:' === substr(__FILE__, 0, 5)) { $tmpExe = sys_get_temp_dir().'/hiddeninput.exe'; copy($exe, $tmpExe); $exe = $tmpExe; } $value = rtrim(shell_exec($exe)); $output->writeln(''); if (isset($tmpExe)) { unlink($tmpExe); } return $value; } if (Terminal::hasSttyAvailable()) { $sttyMode = shell_exec('stty -g'); shell_exec('stty -echo'); $value = fgets($inputStream, 4096); shell_exec(sprintf('stty %s', $sttyMode)); if (false === $value) { throw new RuntimeException('Aborted.'); } $value = trim($value); $output->writeln(''); return $value; } if (false !== $shell = $this->getShell()) { $readCmd = 'csh' === $shell ? 'set mypassword = $<' : 'read -r mypassword'; $command = sprintf("/usr/bin/env %s -c 'stty -echo; %s; stty echo; echo \$mypassword'", $shell, $readCmd); $value = rtrim(shell_exec($command)); $output->writeln(''); return $value; } throw new RuntimeException('Unable to hide the response.'); } 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 \call_user_func($question->getValidator(), $interviewer()); } catch (RuntimeException $e) { throw $e; } catch (\Exception $error) { } } throw $error; } private function getShell() { if (null !== self::$shell) { return self::$shell; } self::$shell = false; if (file_exists('/usr/bin/env')) { $test = "/usr/bin/env %s -c 'echo OK' 2> /dev/null"; foreach (['bash', 'zsh', 'ksh', 'csh'] as $sh) { if ('OK' === rtrim(shell_exec(sprintf($test, $sh)))) { self::$shell = $sh; break; } } } return self::$shell; } } getErrorOutput(); } $formatter = $this->getHelperSet()->get('debug_formatter'); if ($cmd instanceof Process) { $process = $cmd; } else { $process = new Process($cmd); } 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); 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('%s', $this->escapeString($error))); } return $process; } public function mustRun(OutputInterface $output, $cmd, $error = null, callable $callback = null) { $process = $this->run($output, $cmd, $error, $callback); if (!$process->isSuccessful()) { throw new ProcessFailedException($process); } return $process; } public function wrapCallback(OutputInterface $output, Process $process, callable $callback = null) { 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) { \call_user_func($callback, $type, $buffer); } }; } private function escapeString($str) { return str_replace('<', '\\<', $str); } public function getName() { return 'process'; } } 1, 'colspan' => 1, ]; public function __construct($value = '', array $options = []) { if (is_numeric($value) && !\is_string($value)) { $value = (string) $value; } $this->value = $value; 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))); } $this->options = array_merge($this->options, $options); } public function __toString() { return $this->value; } public function getColspan() { return (int) $this->options['colspan']; } public function getRowspan() { return (int) $this->options['rowspan']; } } helperSet = $helperSet; } public function getHelperSet() { return $this->helperSet; } public static function strlen($string) { if (false === $encoding = mb_detect_encoding($string, null, true)) { return \strlen($string); } return mb_strwidth($string, $encoding); } public static function substr($string, $from, $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($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); $string = $formatter->format($string); $string = preg_replace("/\033\[[^m]*m/", '', $string); $formatter->setDecorated($isDecorated); return $string; } } 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(); } public function setMessage($message) { $this->message = $message; $this->display(); } public function start($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(); } 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(); } public function finish($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; } public static function getFormatDefinition($name) { if (!self::$formats) { self::$formats = self::initFormats(); } return isset(self::$formats[$name]) ? self::$formats[$name] : null; } public static function setPlaceholderFormatterDefinition($name, $callable) { if (!self::$formatters) { self::$formatters = self::initPlaceholderFormatters(); } self::$formatters[$name] = $callable; } public static function getPlaceholderFormatterDefinition($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; } $self = $this; $this->overwrite(preg_replace_callback("{%([a-z\-_]+)(?:\:([^%]+))?%}i", function ($matches) use ($self) { if ($formatter = $self::getPlaceholderFormatterDefinition($matches[1])) { return \call_user_func($formatter, $self); } return $matches[0]; }, $this->format)); } private function determineBestFormat() { switch ($this->output->getVerbosity()) { 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'; } } private function overwrite($message) { if ($this->output->isDecorated()) { $this->output->write("\x0D\x1B[2K"); $this->output->write($message); } else { $this->output->writeln($message); } } private function getCurrentTimeInMilliseconds() { return round(microtime(true) * 1000); } private static function initPlaceholderFormatters() { 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() { 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%)', ]; } } '; private $format; private $internalFormat; private $redrawFreq = 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 $firstRun = true; private static $formatters; private static $formats; public function __construct(OutputInterface $output, $max = 0) { if ($output instanceof ConsoleOutputInterface) { $output = $output->getErrorOutput(); } $this->output = $output; $this->setMaxSteps($max); $this->terminal = new Terminal(); if (!$this->output->isDecorated()) { $this->overwrite = false; $this->setRedrawFrequency($max / 10); } $this->startTime = time(); } public static function setPlaceholderFormatterDefinition($name, callable $callable) { if (!self::$formatters) { self::$formatters = self::initPlaceholderFormatters(); } self::$formatters[$name] = $callable; } public static function getPlaceholderFormatterDefinition($name) { if (!self::$formatters) { self::$formatters = self::initPlaceholderFormatters(); } return isset(self::$formatters[$name]) ? self::$formatters[$name] : null; } public static function setFormatDefinition($name, $format) { if (!self::$formats) { self::$formats = self::initFormats(); } self::$formats[$name] = $format; } public static function getFormatDefinition($name) { if (!self::$formats) { self::$formats = self::initFormats(); } return isset(self::$formats[$name]) ? self::$formats[$name] : null; } public function setMessage($message, $name = 'message') { $this->messages[$name] = $message; } public function getMessage($name = 'message') { return $this->messages[$name]; } public function getStartTime() { return $this->startTime; } public function getMaxSteps() { return $this->max; } public function getProgress() { return $this->step; } private function getStepWidth() { return $this->stepWidth; } public function getProgressPercent() { return $this->percent; } public function setBarWidth($size) { $this->barWidth = max(1, (int) $size); } public function getBarWidth() { return $this->barWidth; } public function setBarCharacter($char) { $this->barChar = $char; } public function getBarCharacter() { if (null === $this->barChar) { return $this->max ? '=' : $this->emptyBarChar; } return $this->barChar; } public function setEmptyBarCharacter($char) { $this->emptyBarChar = $char; } public function getEmptyBarCharacter() { return $this->emptyBarChar; } public function setProgressCharacter($char) { $this->progressChar = $char; } public function getProgressCharacter() { return $this->progressChar; } public function setFormat($format) { $this->format = null; $this->internalFormat = $format; } public function setRedrawFrequency($freq) { $this->redrawFreq = max((int) $freq, 1); } public function start($max = null) { $this->startTime = time(); $this->step = 0; $this->percent = 0.0; if (null !== $max) { $this->setMaxSteps($max); } $this->display(); } public function advance($step = 1) { $this->setProgress($this->step + $step); } public function setOverwrite($overwrite) { $this->overwrite = (bool) $overwrite; } public function setProgress($step) { $step = (int) $step; if ($this->max && $step > $this->max) { $this->max = $step; } elseif ($step < 0) { $step = 0; } $prevPeriod = (int) ($this->step / $this->redrawFreq); $currPeriod = (int) ($step / $this->redrawFreq); $this->step = $step; $this->percent = $this->max ? (float) $this->step / $this->max : 0; if ($prevPeriod !== $currPeriod || $this->max === $step) { $this->display(); } } public function finish() { if (!$this->max) { $this->max = $this->step; } if ($this->step === $this->max && !$this->overwrite) { return; } $this->setProgress($this->max); } public function display() { if (OutputInterface::VERBOSITY_QUIET === $this->output->getVerbosity()) { return; } if (null === $this->format) { $this->setRealFormat($this->internalFormat ?: $this->determineBestFormat()); } $this->overwrite($this->buildLine()); } public function clear() { if (!$this->overwrite) { return; } if (null === $this->format) { $this->setRealFormat($this->internalFormat ?: $this->determineBestFormat()); } $this->overwrite(''); } private function setRealFormat($format) { 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"); } private function setMaxSteps($max) { $this->max = max(0, (int) $max); $this->stepWidth = $this->max ? Helper::strlen($this->max) : 4; } private function overwrite($message) { if ($this->overwrite) { if (!$this->firstRun) { if ($this->formatLineCount > 0) { $message = str_repeat("\x1B[1A\x1B[2K", $this->formatLineCount).$message; } $message = "\x0D\x1B[2K$message"; } } elseif ($this->step > 0) { $message = PHP_EOL.$message; } $this->firstRun = false; $this->output->write($message); } private function determineBestFormat() { switch ($this->output->getVerbosity()) { 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() { return [ 'bar' => function (self $bar, OutputInterface $output) { $completeBars = floor($bar->getMaxSteps() > 0 ? $bar->getProgressPercent() * $bar->getBarWidth() : $bar->getProgress() % $bar->getBarWidth()); $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.'); } if (!$bar->getProgress()) { $remaining = 0; } else { $remaining = round((time() - $bar->getStartTime()) / $bar->getProgress() * ($bar->getMaxSteps() - $bar->getProgress())); } return Helper::formatTime($remaining); }, '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.'); } if (!$bar->getProgress()) { $estimated = 0; } else { $estimated = round((time() - $bar->getStartTime()) / $bar->getProgress() * $bar->getMaxSteps()); } return Helper::formatTime($estimated); }, '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() { 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() { $regex = "{%([a-z\-_]+)(?:\:([^%]+))?%}i"; $callback = function ($matches) { if ($formatter = $this::getPlaceholderFormatterDefinition($matches[1])) { $text = \call_user_func($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); $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); } } '; private $errorMessage = 'Value "%s" is invalid'; public function __construct($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); } public function getChoices() { return $this->choices; } public function setMultiselect($multiselect) { $this->multiselect = $multiselect; $this->setValidator($this->getDefaultValidator()); return $this; } public function isMultiselect() { return $this->multiselect; } public function getPrompt() { return $this->prompt; } public function setPrompt($prompt) { $this->prompt = $prompt; return $this; } public function setErrorMessage($errorMessage) { $this->errorMessage = $errorMessage; $this->setValidator($this->getDefaultValidator()); return $this; } private function getDefaultValidator() { $choices = $this->choices; $errorMessage = $this->errorMessage; $multiselect = $this->multiselect; $isAssoc = $this->isAssoc($choices); return function ($selected) use ($choices, $errorMessage, $multiselect, $isAssoc) { if ($multiselect) { if (!preg_match('/^[^,]+(?:,[^,]+)*$/', $selected, $matches)) { throw new InvalidArgumentException(sprintf($errorMessage, $selected)); } $selectedChoices = array_map('trim', explode(',', $selected)); } else { $selectedChoices = [trim($selected)]; } $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)); } $multiselectChoices[] = (string) $result; } if ($multiselect) { return $multiselectChoices; } return current($multiselectChoices); }; } } trueAnswerRegex = $trueAnswerRegex; $this->setNormalizer($this->getDefaultNormalizer()); } private function getDefaultNormalizer() { $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; }; } } question = $question; $this->default = $default; } public function getQuestion() { return $this->question; } public function getDefault() { return $this->default; } public function isHidden() { return $this->hidden; } public function setHidden($hidden) { if ($this->autocompleterValues) { throw new LogicException('A hidden question cannot use the autocompleter.'); } $this->hidden = (bool) $hidden; return $this; } public function isHiddenFallback() { return $this->hiddenFallback; } public function setHiddenFallback($fallback) { $this->hiddenFallback = (bool) $fallback; return $this; } public function getAutocompleterValues() { return $this->autocompleterValues; } public function setAutocompleterValues($values) { if (\is_array($values)) { $values = $this->isAssoc($values) ? array_merge(array_keys($values), array_values($values)) : array_values($values); } if (null !== $values && !\is_array($values) && !$values instanceof \Traversable) { throw new InvalidArgumentException('Autocompleter values can be either an array, `null` or a `Traversable` object.'); } if ($this->hidden) { throw new LogicException('A hidden question cannot use the autocompleter.'); } $this->autocompleterValues = $values; return $this; } public function setValidator(callable $validator = null) { $this->validator = $validator; return $this; } public function getValidator() { return $this->validator; } public function setMaxAttempts($attempts) { if (null !== $attempts && $attempts < 1) { throw new InvalidArgumentException('Maximum number of attempts must be a positive value.'); } $this->attempts = $attempts; return $this; } public function getMaxAttempts() { return $this->attempts; } public function setNormalizer(callable $normalizer) { $this->normalizer = $normalizer; return $this; } public function getNormalizer() { return $this->normalizer; } protected function isAssoc($array) { return (bool) \count(array_filter(array_keys($array), 'is_string')); } } command = $command; } public function execute(array $input, array $options = []) { 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); $this->input->setStream(self::createStream($this->inputs)); if (isset($options['interactive'])) { $this->input->setInteractive($options['interactive']); } $this->output = new StreamOutput(fopen('php://memory', 'w', false)); $this->output->setDecorated(isset($options['decorated']) ? $options['decorated'] : false); if (isset($options['verbosity'])) { $this->output->setVerbosity($options['verbosity']); } return $this->statusCode = $this->command->run($this->input, $this->output); } public function getDisplay($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; } public function getInput() { return $this->input; } public function getOutput() { return $this->output; } public function getStatusCode() { return $this->statusCode; } public function setInputs(array $inputs) { $this->inputs = $inputs; return $this; } 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; } } application = $application; } public function run(array $input, $options = []) { $this->input = new ArrayInput($input); if (isset($options['interactive'])) { $this->input->setInteractive($options['interactive']); } $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 $this->statusCode = $this->application->run($this->input, $this->output); } public function getDisplay($normalize = false) { rewind($this->output->getStream()); $display = stream_get_contents($this->output->getStream()); if ($normalize) { $display = str_replace(PHP_EOL, "\n", $display); } return $display; } public function getErrorOutput($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; } public function getInput() { return $this->input; } public function getOutput() { return $this->output; } public function getStatusCode() { return $this->statusCode; } } alternatives = $alternatives; } public function getAlternatives() { return $this->alternatives; } } output = $output; } public function newLine($count = 1) { $this->output->write(str_repeat(PHP_EOL, $count)); } public function createProgressBar($max = 0) { return new ProgressBar($this->output, $max); } public function write($messages, $newline = false, $type = self::OUTPUT_NORMAL) { $this->output->write($messages, $newline, $type); } public function writeln($messages, $type = self::OUTPUT_NORMAL) { $this->output->writeln($messages, $type); } public function setVerbosity($level) { $this->output->setVerbosity($level); } public function getVerbosity() { return $this->output->getVerbosity(); } public function setDecorated($decorated) { $this->output->setDecorated($decorated); } public function isDecorated() { return $this->output->isDecorated(); } public function setFormatter(OutputFormatterInterface $formatter) { $this->output->setFormatter($formatter); } public function getFormatter() { return $this->output->getFormatter(); } public function isQuiet() { return $this->output->isQuiet(); } public function isVerbose() { return $this->output->isVerbose(); } public function isVeryVerbose() { return $this->output->isVeryVerbose(); } public function isDebug() { return $this->output->isDebug(); } protected function getErrorOutput() { if (!$this->output instanceof ConsoleOutputInterface) { return $this->output; } return $this->output->getErrorOutput(); } } input = $input; $this->bufferedOutput = new BufferedOutput($output->getVerbosity(), false, clone $output->getFormatter()); $width = (new Terminal())->getWidth() ?: self::MAX_LINE_LENGTH; $this->lineLength = min($width - (int) (\DIRECTORY_SEPARATOR === '\\'), self::MAX_LINE_LENGTH); parent::__construct($output); } public function block($messages, $type = null, $style = null, $prefix = ' ', $padding = false, $escape = true) { $messages = \is_array($messages) ? array_values($messages) : [$messages]; $this->autoPrependBlock(); $this->writeln($this->createBlock($messages, $type, $style, $prefix, $padding, $escape)); $this->newLine(); } public function title($message) { $this->autoPrependBlock(); $this->writeln([ sprintf('%s', OutputFormatter::escapeTrailingBackslash($message)), sprintf('%s', str_repeat('=', Helper::strlenWithoutDecoration($this->getFormatter(), $message))), ]); $this->newLine(); } public function section($message) { $this->autoPrependBlock(); $this->writeln([ sprintf('%s', OutputFormatter::escapeTrailingBackslash($message)), sprintf('%s', str_repeat('-', Helper::strlenWithoutDecoration($this->getFormatter(), $message))), ]); $this->newLine(); } public function listing(array $elements) { $this->autoPrependText(); $elements = array_map(function ($element) { return sprintf(' * %s', $element); }, $elements); $this->writeln($elements); $this->newLine(); } public function text($message) { $this->autoPrependText(); $messages = \is_array($message) ? array_values($message) : [$message]; foreach ($messages as $message) { $this->writeln(sprintf(' %s', $message)); } } public function comment($message) { $this->block($message, null, null, ' // ', false, false); } public function success($message) { $this->block($message, 'OK', 'fg=black;bg=green', ' ', true); } public function error($message) { $this->block($message, 'ERROR', 'fg=white;bg=red', ' ', true); } public function warning($message) { $this->block($message, 'WARNING', 'fg=white;bg=red', ' ', true); } public function note($message) { $this->block($message, 'NOTE', 'fg=yellow', ' ! '); } public function caution($message) { $this->block($message, 'CAUTION', 'fg=white;bg=red', ' ! ', true); } public function table(array $headers, array $rows) { $style = clone Table::getStyleDefinition('symfony-style-guide'); $style->setCellHeaderFormat('%s'); $table = new Table($this); $table->setHeaders($headers); $table->setRows($rows); $table->setStyle($style); $table->render(); $this->newLine(); } public function ask($question, $default = null, $validator = null) { $question = new Question($question, $default); $question->setValidator($validator); return $this->askQuestion($question); } public function askHidden($question, $validator = null) { $question = new Question($question); $question->setHidden(true); $question->setValidator($validator); return $this->askQuestion($question); } public function confirm($question, $default = true) { return $this->askQuestion(new ConfirmationQuestion($question, $default)); } public function choice($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)); } public function progressStart($max = 0) { $this->progressBar = $this->createProgressBar($max); $this->progressBar->start(); } public function progressAdvance($step = 1) { $this->getProgressBar()->advance($step); } public function progressFinish() { $this->getProgressBar()->finish(); $this->newLine(2); $this->progressBar = null; } public function createProgressBar($max = 0) { $progressBar = parent::createProgressBar($max); if ('\\' !== \DIRECTORY_SEPARATOR || 'Hyper' === getenv('TERM_PROGRAM')) { $progressBar->setEmptyBarCharacter('░'); $progressBar->setProgressCharacter(''); $progressBar->setBarCharacter('▓'); } return $progressBar; } 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; } public function writeln($messages, $type = self::OUTPUT_NORMAL) { parent::writeln($messages, $type); $this->bufferedOutput->writeln($this->reduceBuffer($messages), $type); } public function write($messages, $newline = false, $type = self::OUTPUT_NORMAL) { parent::write($messages, $newline, $type); $this->bufferedOutput->write($this->reduceBuffer($messages), $newline, $type); } public function newLine($count = 1) { parent::newLine($count); $this->bufferedOutput->write(str_repeat("\n", $count)); } public function getErrorStyle() { return new self($this->input, $this->getErrorOutput()); } private function getProgressBar() { if (!$this->progressBar) { throw new RuntimeException('The ProgressBar is not started.'); } return $this->progressBar; } private function autoPrependBlock() { $chars = substr(str_replace(PHP_EOL, "\n", $this->bufferedOutput->fetch()), -2); if (!isset($chars[0])) { $this->newLine(); return; } $this->newLine(2 - substr_count($chars, "\n")); } private function autoPrependText() { $fetched = $this->bufferedOutput->fetch(); if ("\n" !== substr($fetched, -1)) { $this->newLine(); } } private function reduceBuffer($messages) { return array_map(function ($value) { return substr($value, -4); }, array_merge([$this->bufferedOutput->fetch()], (array) $messages)); } private function createBlock($messages, $type = null, $style = null, $prefix = ' ', $padding = false, $escape = false) { $indentLength = 0; $prefixLength = Helper::strlenWithoutDecoration($this->getFormatter(), $prefix); $lines = []; if (null !== $type) { $type = sprintf('[%s] ', $type); $indentLength = \strlen($type); $lineIndentation = str_repeat(' ', $indentLength); } 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; } } 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) { $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; } } setName('list') ->setDefinition($this->createDefinition()) ->setDescription('Lists commands') ->setHelp(<<<'EOF' The %command.name% command lists all commands: php %command.full_name% You can also display the commands for a specific namespace: php %command.full_name% test You can also output the information in other formats by using the --format option: php %command.full_name% --format=xml It's also possible to get raw list of commands (useful for embedding command runner): php %command.full_name% --raw EOF ) ; } public function getNativeDefinition() { return $this->createDefinition(); } 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'), ]); } private function createDefinition() { return new InputDefinition([ 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'), ]); } } class ? static::$defaultName : null; } public function __construct($name = null) { $this->definition = new InputDefinition(); if (null !== $name || null !== $name = static::getDefaultName()) { $this->setName($name); } $this->configure(); } 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; } } public function setHelperSet(HelperSet $helperSet) { $this->helperSet = $helperSet; } public function getHelperSet() { return $this->helperSet; } public function getApplication() { return $this->application; } public function isEnabled() { return true; } protected function configure() { } protected function execute(InputInterface $input, OutputInterface $output) { throw new LogicException('You must override the execute() method in the concrete command class.'); } protected function interact(InputInterface $input, OutputInterface $output) { } protected function initialize(InputInterface $input, OutputInterface $output) { } public function run(InputInterface $input, OutputInterface $output) { $this->getSynopsis(true); $this->getSynopsis(false); $this->mergeApplicationDefinition(); try { $input->bind($this->definition); } 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('Running "cli_set_process_title" as an unprivileged user is not supported on MacOS.', 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('Install the proctitle PECL to be able to change the process title.'); } } if ($input->isInteractive()) { $this->interact($input, $output); } if ($input->hasArgument('command') && null === $input->getArgument('command')) { $input->setArgument('command', $this->getName()); } $input->validate(); if ($this->code) { $statusCode = \call_user_func($this->code, $input, $output); } else { $statusCode = $this->execute($input, $output); } return is_numeric($statusCode) ? (int) $statusCode : 0; } public function setCode(callable $code) { if ($code instanceof \Closure) { $r = new \ReflectionFunction($code); if (null === $r->getClosureThis()) { if (\PHP_VERSION_ID < 70000) { $code = @\Closure::bind($code, $this); } else { $code = \Closure::bind($code, $this); } } } $this->code = $code; return $this; } public function mergeApplicationDefinition($mergeArgs = true) { if (null === $this->application || (true === $this->applicationDefinitionMerged && ($this->applicationDefinitionMergedWithArgs || !$mergeArgs))) { return; } $this->definition->addOptions($this->application->getDefinition()->getOptions()); $this->applicationDefinitionMerged = true; if ($mergeArgs) { $currentArguments = $this->definition->getArguments(); $this->definition->setArguments($this->application->getDefinition()->getArguments()); $this->definition->addArguments($currentArguments); $this->applicationDefinitionMergedWithArgs = true; } } public function setDefinition($definition) { if ($definition instanceof InputDefinition) { $this->definition = $definition; } else { $this->definition->setDefinition($definition); } $this->applicationDefinitionMerged = false; return $this; } public function getDefinition() { 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; } public function getNativeDefinition() { return $this->getDefinition(); } public function addArgument($name, $mode = null, $description = '', $default = null) { $this->definition->addArgument(new InputArgument($name, $mode, $description, $default)); return $this; } public function addOption($name, $shortcut = null, $mode = null, $description = '', $default = null) { $this->definition->addOption(new InputOption($name, $shortcut, $mode, $description, $default)); return $this; } public function setName($name) { $this->validateName($name); $this->name = $name; return $this; } public function setProcessTitle($title) { $this->processTitle = $title; return $this; } public function getName() { return $this->name; } public function setHidden($hidden) { $this->hidden = (bool) $hidden; return $this; } public function isHidden() { return $this->hidden; } public function setDescription($description) { $this->description = $description; return $this; } public function getDescription() { return $this->description; } public function setHelp($help) { $this->help = $help; return $this; } public function getHelp() { return $this->help; } 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()); } public function setAliases($aliases) { if (!\is_array($aliases) && !$aliases instanceof \Traversable) { throw new InvalidArgumentException('$aliases must be an array or an instance of \Traversable.'); } foreach ($aliases as $alias) { $this->validateName($alias); } $this->aliases = $aliases; return $this; } public function getAliases() { return $this->aliases; } public function getSynopsis($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]; } public function addUsage($usage) { if (0 !== strpos($usage, $this->name)) { $usage = sprintf('%s %s', $this->name, $usage); } $this->usages[] = $usage; return $this; } public function getUsages() { return $this->usages; } public function getHelper($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); } private function validateName($name) { if (!preg_match('/^[^\:]++(\:[^\:]++)*$/', $name)) { throw new InvalidArgumentException(sprintf('Command name "%s" is invalid.', $name)); } } } lock) { throw new LogicException('A lock is already in place.'); } if (SemaphoreStore::isSupported($blocking)) { $store = new SemaphoreStore(); } else { $store = new FlockStore(); } $this->lock = (new Factory($store))->createLock($name ?: $this->getName()); if (!$this->lock->acquire($blocking)) { $this->lock = null; return false; } return true; } private function release() { if ($this->lock) { $this->lock->release(); $this->lock = null; } } } 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 %command.name% command displays help for a given command: php %command.full_name% list You can also output the help in other formats by using the --format option: php %command.full_name% --format=xml list To display the list of available commands, please use the list command. EOF ) ; } public function setCommand(Command $command) { $this->command = $command; } 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; } } commandLoaderServiceId = $commandLoaderServiceId; $this->commandTag = $commandTag; } public function process(ContainerBuilder $container) { $commandServices = $container->findTaggedServiceIds($this->commandTag, true); $lazyCommandMap = []; $lazyCommandRefs = []; $serviceIds = []; $lazyServiceIds = []; foreach ($commandServices as $id => $tags) { $definition = $container->getDefinition($id); $class = $container->getParameterBag()->resolveValue($definition->getClass()); $commandId = 'console.command.'.strtolower(str_replace('\\', '_', $class)); 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 (isset($serviceIds[$commandId]) || $container->hasAlias($commandId)) { $commandId = $commandId.'_'.$id; } if (!$definition->isPublic() || $definition->isPrivate()) { $container->setAlias($commandId, $id)->setPublic(true); $id = $commandId; } $serviceIds[$commandId] = $id; continue; } $serviceIds[$commandId] = $id; $lazyServiceIds[$id] = true; 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) ->setArguments([ServiceLocatorTagPass::register($container, $lazyCommandRefs), $lazyCommandMap]); $container->setParameter('console.command.ids', $serviceIds); $container->setParameter('console.lazy_command.ids', $lazyServiceIds); } } &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)) { self::$width = (int) $matches[1]; self::$height = isset($matches[4]) ? (int) $matches[4] : (int) $matches[2]; } elseif (!self::hasVt100Support() && self::hasSttyAvailable()) { self::initDimensionsUsingStty(); } elseif (null !== $dimensions = self::getConsoleMode()) { self::$width = (int) $dimensions[0]; self::$height = (int) $dimensions[1]; } } else { self::initDimensionsUsingStty(); } } private static function hasVt100Support() { return \function_exists('sapi_windows_vt100_support') && sapi_windows_vt100_support(fopen('php://stdout', 'w')); } private static function initDimensionsUsingStty() { if ($sttyString = self::getSttyColumns()) { if (preg_match('/rows.(\d+);.columns.(\d+);/i', $sttyString, $matches)) { self::$width = (int) $matches[2]; self::$height = (int) $matches[1]; } elseif (preg_match('/;.(\d+).rows;.(\d+).columns/i', $sttyString, $matches)) { self::$width = (int) $matches[2]; self::$height = (int) $matches[1]; } } } private static function getConsoleMode() { $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]]; } private static function getSttyColumns() { return self::readFromProcess('stty -a | grep columns'); } private static function readFromProcess($command) { 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; } } ['set' => 30, 'unset' => 39], 'red' => ['set' => 31, 'unset' => 39], 'green' => ['set' => 32, 'unset' => 39], 'yellow' => ['set' => 33, 'unset' => 39], 'blue' => ['set' => 34, 'unset' => 39], 'magenta' => ['set' => 35, 'unset' => 39], 'cyan' => ['set' => 36, 'unset' => 39], 'white' => ['set' => 37, 'unset' => 39], 'default' => ['set' => 39, 'unset' => 39], ]; private static $availableBackgroundColors = [ 'black' => ['set' => 40, 'unset' => 49], 'red' => ['set' => 41, 'unset' => 49], 'green' => ['set' => 42, 'unset' => 49], 'yellow' => ['set' => 43, 'unset' => 49], 'blue' => ['set' => 44, 'unset' => 49], 'magenta' => ['set' => 45, 'unset' => 49], 'cyan' => ['set' => 46, 'unset' => 49], 'white' => ['set' => 47, 'unset' => 49], 'default' => ['set' => 49, 'unset' => 49], ]; private static $availableOptions = [ '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($foreground = null, $background = null, array $options = []) { if (null !== $foreground) { $this->setForeground($foreground); } if (null !== $background) { $this->setBackground($background); } if (\count($options)) { $this->setOptions($options); } } public function setForeground($color = null) { if (null === $color) { $this->foreground = null; return; } if (!isset(static::$availableForegroundColors[$color])) { throw new InvalidArgumentException(sprintf('Invalid foreground color specified: "%s". Expected one of (%s).', $color, implode(', ', array_keys(static::$availableForegroundColors)))); } $this->foreground = static::$availableForegroundColors[$color]; } public function setBackground($color = null) { if (null === $color) { $this->background = null; return; } if (!isset(static::$availableBackgroundColors[$color])) { throw new InvalidArgumentException(sprintf('Invalid background color specified: "%s". Expected one of (%s).', $color, implode(', ', array_keys(static::$availableBackgroundColors)))); } $this->background = static::$availableBackgroundColors[$color]; } public function setOption($option) { if (!isset(static::$availableOptions[$option])) { throw new InvalidArgumentException(sprintf('Invalid option specified: "%s". Expected one of (%s).', $option, implode(', ', array_keys(static::$availableOptions)))); } if (!\in_array(static::$availableOptions[$option], $this->options)) { $this->options[] = static::$availableOptions[$option]; } } public function unsetOption($option) { if (!isset(static::$availableOptions[$option])) { throw new InvalidArgumentException(sprintf('Invalid option specified: "%s". Expected one of (%s).', $option, implode(', ', array_keys(static::$availableOptions)))); } $pos = array_search(static::$availableOptions[$option], $this->options); if (false !== $pos) { unset($this->options[$pos]); } } public function setOptions(array $options) { $this->options = []; foreach ($options as $option) { $this->setOption($option); } } public function apply($text) { $setCodes = []; $unsetCodes = []; if (null !== $this->foreground) { $setCodes[] = $this->foreground['set']; $unsetCodes[] = $this->foreground['unset']; } if (null !== $this->background) { $setCodes[] = $this->background['set']; $unsetCodes[] = $this->background['unset']; } if (\count($this->options)) { foreach ($this->options as $option) { $setCodes[] = $option['set']; $unsetCodes[] = $option['unset']; } } if (0 === \count($setCodes)) { return $text; } return sprintf("\033[%sm%s\033[%sm", implode(';', $setCodes), $text, implode(';', $unsetCodes)); } } decorated = (bool) $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(); } public function setDecorated($decorated) { $this->decorated = (bool) $decorated; } public function isDecorated() { return $this->decorated; } public function setStyle($name, OutputFormatterStyleInterface $style) { $this->styles[strtolower($name)] = $style; } public function hasStyle($name) { return isset($this->styles[strtolower($name)]); } public function getStyle($name) { if (!$this->hasStyle($name)) { throw new InvalidArgumentException(sprintf('Undefined style: "%s".', $name)); } return $this->styles[strtolower($name)]; } public function format($message) { $message = (string) $message; $offset = 0; $output = ''; $tagRegex = '[a-z][a-z0-9,_=;-]*+'; 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; } $output .= $this->applyCurrentStyle(substr($message, $offset, $pos - $offset)); $offset = $pos + \strlen($text); 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 (false === $style = $this->createStyleFromString($tag)) { $output .= $this->applyCurrentStyle($text); } elseif ($open) { $this->styleStack->push($style); } else { $this->styleStack->pop($style); } } $output .= $this->applyCurrentStyle(substr($message, $offset)); if (false !== strpos($output, "\0")) { return strtr($output, ["\0" => '\\', '\\<' => '<']); } return str_replace('\\<', '<', $output); } public function getStyleStack() { return $this->styleStack; } private function createStyleFromString($string) { if (isset($this->styles[$string])) { return $this->styles[$string]; } if (!preg_match_all('/([^=]+)=([^;]+)(;|$)/', $string, $matches, PREG_SET_ORDER)) { return false; } $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 ('options' === $match[0]) { preg_match_all('([^,;]+)', strtolower($match[1]), $options); $options = array_shift($options); foreach ($options as $option) { try { $style->setOption($option); } catch (\InvalidArgumentException $e) { @trigger_error(sprintf('Unknown style options are deprecated since Symfony 3.2 and will be removed in 4.0. Exception "%s".', $e->getMessage()), E_USER_DEPRECATED); return false; } } } else { return false; } } return $style; } private function applyCurrentStyle($text) { return $this->isDecorated() && \strlen($text) > 0 ? $this->styleStack->getCurrent()->apply($text) : $text; } } emptyStyle = $emptyStyle ?: new OutputFormatterStyle(); $this->reset(); } public function reset() { $this->styles = []; } public function push(OutputFormatterStyleInterface $style) { $this->styles[] = $style; } 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.'); } public function getCurrent() { if (empty($this->styles)) { return $this->emptyStyle; } return $this->styles[\count($this->styles) - 1]; } public function setEmptyStyle(OutputFormatterStyleInterface $emptyStyle) { $this->emptyStyle = $emptyStyle; return $this; } public function getEmptyStyle() { return $this->emptyStyle; } } writeData($this->getInputArgumentData($argument), $options); } protected function describeInputOption(InputOption $option, array $options = []) { $this->writeData($this->getInputOptionData($option), $options); } protected function describeInputDefinition(InputDefinition $definition, array $options = []) { $this->writeData($this->getInputDefinitionData($definition), $options); } protected function describeCommand(Command $command, array $options = []) { $this->writeData($this->getCommandData($command), $options); } 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); } 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) { 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) { 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) { $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) { $command->getSynopsis(); $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->getNativeDefinition()), 'hidden' => $command->isHidden(), ]; } } application = $application; $this->namespace = $namespace; $this->showHidden = $showHidden; } public function getNamespaces() { if (null === $this->namespaces) { $this->inspectApplication(); } return $this->namespaces; } public function getCommands() { if (null === $this->commands) { $this->inspectApplication(); } return $this->commands; } public function getCommand($name) { 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 = []; 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) { $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; } } 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) { $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->appendChild($commandXML = $dom->createElement('command')); $command->getSynopsis(); $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->getNativeDefinition()); $this->appendDocument($commandXML, $definitionXML->getElementsByTagName('definition')->item(0)); return $dom; } public function getApplicationDocument(Application $application, $namespace = null) { $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; } protected function describeInputArgument(InputArgument $argument, array $options = []) { $this->writeDocument($this->getInputArgumentDocument($argument)); } protected function describeInputOption(InputOption $option, array $options = []) { $this->writeDocument($this->getInputOptionDocument($option)); } protected function describeInputDefinition(InputDefinition $definition, array $options = []) { $this->writeDocument($this->getInputDefinitionDocument($definition)); } protected function describeCommand(Command $command, array $options = []) { $this->writeDocument($this->getCommandDocument($command)); } protected function describeApplication(Application $application, array $options = []) { $this->writeDocument($this->getApplicationDocument($application, isset($options['namespace']) ? $options['namespace'] : null)); } private function appendDocument(\DOMNode $parentNode, \DOMNode $importedParent) { foreach ($importedParent->childNodes as $childNode) { $parentNode->appendChild($parentNode->ownerDocument->importNode($childNode, true)); } } private function writeDocument(\DOMDocument $dom) { $dom->formatOutput = true; $this->write($dom->saveXML()); } private function getInputArgumentDocument(InputArgument $argument) { $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) { $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; } } getDefault() && (!\is_array($argument->getDefault()) || \count($argument->getDefault()))) { $default = sprintf(' [default: %s]', $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(' %s %s%s%s', $argument->getName(), str_repeat(' ', $spacingWidth), preg_replace('/\s*[\r\n]\s*/', "\n".str_repeat(' ', $totalWidth + 4), $argument->getDescription()), $default ), $options); } protected function describeInputOption(InputOption $option, array $options = []) { if ($option->acceptValue() && null !== $option->getDefault() && (!\is_array($option->getDefault()) || \count($option->getDefault()))) { $default = sprintf(' [default: %s]', $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(' %s %s%s%s%s', $synopsis, str_repeat(' ', $spacingWidth), preg_replace('/\s*[\r\n]\s*/', "\n".str_repeat(' ', $totalWidth + 4), $option->getDescription()), $default, $option->isArray() ? ' (multiple values allowed)' : '' ), $options); } 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('Arguments:', $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('Options:', $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])); } } } protected function describeCommand(Command $command, array $options = []) { $command->getSynopsis(true); $command->getSynopsis(false); $command->mergeApplicationDefinition(false); $this->writeText('Usage:', $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->getNativeDefinition(); if ($definition->getOptions() || $definition->getArguments()) { $this->writeText("\n"); $this->describeInputDefinition($definition, $options); $this->writeText("\n"); } if ($help = $command->getProcessedHelp()) { $this->writeText("\n"); $this->writeText('Help:', $options); $this->writeText("\n"); $this->writeText(' '.str_replace("\n", "\n ", $help), $options); $this->writeText("\n"); } } 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("Usage:\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) { $describedNamespaceInfo = reset($namespaces); foreach ($describedNamespaceInfo['commands'] as $name) { $commands[$name] = $description->getCommand($name); } } $width = $this->getColumnWidth(\call_user_func_array('array_merge', array_map(function ($namespace) use ($commands) { return array_intersect($namespace['commands'], array_keys($commands)); }, $namespaces))); if ($describedNamespace) { $this->writeText(sprintf('Available commands for the "%s" namespace:', $describedNamespace), $options); } else { $this->writeText('Available commands:', $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(' '.$namespace['id'].'', $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(' %s%s%s', $name, str_repeat(' ', $spacingWidth), $commandAliases.$command->getDescription()), $options); } } $this->writeText("\n"); } } private function writeText($content, array $options = []) { $this->write( isset($options['raw_text']) && $options['raw_text'] ? strip_tags($content) : $content, isset($options['raw_output']) ? !$options['raw_output'] : true ); } private function getCommandAliasesText(Command $command) { $text = ''; $aliases = $command->getAliases(); if ($aliases) { $text = '['.implode('|', $aliases).'] '; } return $text; } private function formatDefaultValue($default) { 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)); } private function getColumnWidth(array $commands) { $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; } private function calculateTotalWidthForOptions(array $options) { $totalWidth = 0; foreach ($options as $option) { $nameLength = 1 + max(Helper::strlen($option->getShortcut()), 1) + 4 + Helper::strlen($option->getName()); if ($option->acceptValue()) { $valueLength = 1 + Helper::strlen($option->getName()); $valueLength += $option->isValueOptional() ? 2 : 0; $nameLength += $valueLength; } $totalWidth = max($totalWidth, $nameLength); } return $totalWidth; } } 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_class($object))); } } protected function write($content, $decorated = false) { $this->output->write($content, false, $decorated ? OutputInterface::OUTPUT_NORMAL : OutputInterface::OUTPUT_RAW); } abstract protected function describeInputArgument(InputArgument $argument, array $options = []); abstract protected function describeInputOption(InputOption $option, array $options = []); abstract protected function describeInputDefinition(InputDefinition $definition, array $options = []); abstract protected function describeCommand(Command $command, array $options = []); abstract protected function describeApplication(Application $application, array $options = []); } isDecorated(); $output->setDecorated(false); parent::describe($output, $object, $options); $output->setDecorated($decorated); } protected function write($content, $decorated = true) { parent::write($content, $decorated); } protected function describeInputArgument(InputArgument $argument, array $options = []) { $this->write( '#### `'.($argument->getName() ?: '')."`\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)).'`' ); } 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)).'`' ); } 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"); $this->write($this->describeInputArgument($argument)); } } if (\count($definition->getOptions()) > 0) { if ($showArguments) { $this->write("\n\n"); } $this->write('### Options'); foreach ($definition->getOptions() as $option) { $this->write("\n\n"); $this->write($this->describeInputOption($option)); } } } protected function describeCommand(Command $command, array $options = []) { $command->getSynopsis(); $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); } if ($command->getNativeDefinition()) { $this->write("\n\n"); $this->describeInputDefinition($command->getNativeDefinition()); } } 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"); $this->write($this->describeCommand($command)); } } private function getApplicationTitle(Application $application) { if ('UNKNOWN' !== $application->getName()) { if ('UNKNOWN' !== $application->getVersion()) { return sprintf('%s %s', $application->getName(), $application->getVersion()); } return $application->getName(); } return 'Console Tool'; } } definition = new InputDefinition(); } else { $this->bind($definition); $this->validate(); } } public function bind(InputDefinition $definition) { $this->arguments = []; $this->options = []; $this->definition = $definition; $this->parse(); } abstract protected function parse(); 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))); } } public function isInteractive() { return $this->interactive; } public function setInteractive($interactive) { $this->interactive = (bool) $interactive; } public function getArguments() { return array_merge($this->definition->getArgumentDefaults(), $this->arguments); } public function getArgument($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(); } public function setArgument($name, $value) { if (!$this->definition->hasArgument($name)) { throw new InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); } $this->arguments[$name] = $value; } public function hasArgument($name) { return $this->definition->hasArgument($name); } public function getOptions() { return array_merge($this->definition->getOptionDefaults(), $this->options); } public function getOption($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(); } public function setOption($name, $value) { if (!$this->definition->hasOption($name)) { throw new InvalidArgumentException(sprintf('The "%s" option does not exist.', $name)); } $this->options[$name] = $value; } public function hasOption($name) { return $this->definition->hasOption($name); } public function escapeToken($token) { return preg_match('{^[\w-]+$}', $token) ? $token : escapeshellarg($token); } public function setStream($stream) { $this->stream = $stream; } public function getStream() { return $this->stream; } } setDefinition($definition); } 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); } public function setArguments($arguments = []) { $this->arguments = []; $this->requiredCount = 0; $this->hasOptional = false; $this->hasAnArrayArgument = false; $this->addArguments($arguments); } public function addArguments($arguments = []) { if (null !== $arguments) { foreach ($arguments as $argument) { $this->addArgument($argument); } } } 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; } 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]; } public function hasArgument($name) { $arguments = \is_int($name) ? array_values($this->arguments) : $this->arguments; return isset($arguments[$name]); } public function getArguments() { return $this->arguments; } public function getArgumentCount() { return $this->hasAnArrayArgument ? PHP_INT_MAX : \count($this->arguments); } public function getArgumentRequiredCount() { return $this->requiredCount; } public function getArgumentDefaults() { $values = []; foreach ($this->arguments as $argument) { $values[$argument->getName()] = $argument->getDefault(); } return $values; } public function setOptions($options = []) { $this->options = []; $this->shortcuts = []; $this->addOptions($options); } public function addOptions($options = []) { foreach ($options as $option) { $this->addOption($option); } } 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(); } } } public function getOption($name) { if (!$this->hasOption($name)) { throw new InvalidArgumentException(sprintf('The "--%s" option does not exist.', $name)); } return $this->options[$name]; } public function hasOption($name) { return isset($this->options[$name]); } public function getOptions() { return $this->options; } public function hasShortcut($name) { return isset($this->shortcuts[$name]); } public function getOptionForShortcut($shortcut) { return $this->getOption($this->shortcutToName($shortcut)); } public function getOptionDefaults() { $values = []; foreach ($this->options as $option) { $values[$option->getName()] = $option->getDefault(); } return $values; } public function shortcutToName($shortcut) { if (!isset($this->shortcuts[$shortcut])) { throw new InvalidArgumentException(sprintf('The "-%s" option does not exist.', $shortcut)); } return $this->shortcuts[$shortcut]; } public function getSynopsis($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[] = '[--]'; } foreach ($this->getArguments() as $argument) { $element = '<'.$argument->getName().'>'; if (!$argument->isRequired()) { $element = '['.$element.']'; } elseif ($argument->isArray()) { $element .= ' ('.$element.')'; } if ($argument->isArray()) { $element .= '...'; } $elements[] = $element; } return implode(' ', $elements); } } parameters = $parameters; parent::__construct($definition); } public function getFirstArgument() { foreach ($this->parameters as $param => $value) { if ($param && \is_string($param) && '-' === $param[0]) { continue; } return $value; } return null; } public function hasParameterOption($values, $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; } public function getParameterOption($values, $default = false, $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; } 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); } 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); } } } private function addShortOption($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); } private function addLongOption($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; } 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; } } 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); } public function getShortcut() { return $this->shortcut; } public function getName() { return $this->name; } public function acceptValue() { return $this->isValueRequired() || $this->isValueOptional(); } public function isValueRequired() { return self::VALUE_REQUIRED === (self::VALUE_REQUIRED & $this->mode); } public function isValueOptional() { return self::VALUE_OPTIONAL === (self::VALUE_OPTIONAL & $this->mode); } public function isArray() { return self::VALUE_IS_ARRAY === (self::VALUE_IS_ARRAY & $this->mode); } 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; } public function getDefault() { return $this->default; } public function getDescription() { return $this->description; } 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() ; } } 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); } public function getName() { return $this->name; } public function isRequired() { return self::REQUIRED === (self::REQUIRED & $this->mode); } public function isArray() { return self::IS_ARRAY === (self::IS_ARRAY & $this->mode); } 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; } public function getDefault() { return $this->default; } public function getDescription() { return $this->description; } } setTokens($this->tokenize($input)); } private function tokenize($input) { $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 { throw new InvalidArgumentException(sprintf('Unable to parse input near "... %s ...".', substr($input, $cursor, 10))); } $cursor += \strlen($match[0]); } return $tokens; } } tokens = $argv; parent::__construct($definition); } protected function setTokens(array $tokens) { $this->tokens = $tokens; } 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); } } } private function parseShortOption($token) { $name = substr($token, 1); if (\strlen($name) > 1) { if ($this->definition->hasShortcut($name[0]) && $this->definition->getOptionForShortcut($name[0])->acceptValue()) { $this->addShortOption($name[0], substr($name, 1)); } else { $this->parseShortOptionSet($name); } } else { $this->addShortOption($name, null); } } private function parseShortOptionSet($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); } } } private function parseLongOption($token) { $name = substr($token, 2); if (false !== $pos = strpos($name, '=')) { if (0 === \strlen($value = substr($name, $pos + 1))) { if (\PHP_VERSION_ID < 70000 && false === $value) { $value = ''; } array_unshift($this->parsed, $value); } $this->addLongOption(substr($name, 0, $pos), $value); } else { $this->addLongOption($name, null); } } private function parseArgument($token) { $c = \count($this->arguments); if ($this->definition->hasArgument($c)) { $arg = $this->definition->getArgument($c); $this->arguments[$arg->getName()] = $arg->isArray() ? [$token] : $token; } elseif ($this->definition->hasArgument($c - 1) && $this->definition->getArgument($c - 1)->isArray()) { $arg = $this->definition->getArgument($c - 1); $this->arguments[$arg->getName()][] = $token; } else { $all = $this->definition->getArguments(); if (\count($all)) { throw new RuntimeException(sprintf('Too many arguments, expected arguments "%s".', implode('" "', array_keys($all)))); } throw new RuntimeException(sprintf('No arguments expected, got "%s".', $token)); } } private function addShortOption($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); } private function addLongOption($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)) { $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; } } 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; } $name = '-' === $token[1] ? substr($token, 2) : substr($token, -1); if (!isset($this->options[$name]) && !$this->definition->hasShortcut($name)) { } 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; } public function hasParameterOption($values, $onlyParams = false) { $values = (array) $values; foreach ($this->tokens as $token) { if ($onlyParams && '--' === $token) { return false; } foreach ($values as $value) { $leading = 0 === strpos($value, '--') ? $value.'=' : $value; if ($token === $value || '' !== $leading && 0 === strpos($token, $leading)) { return true; } } } return false; } public function getParameterOption($values, $default = false, $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); } $leading = 0 === strpos($value, '--') ? $value.'=' : $value; if ('' !== $leading && 0 === strpos($token, $leading)) { return substr($token, \strlen($leading)); } } } return $default; } 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); } } container = $container; $this->commandMap = $commandMap; } public function get($name) { if (!$this->has($name)) { throw new CommandNotFoundException(sprintf('Command "%s" does not exist.', $name)); } return $this->container->get($this->commandMap[$name]); } public function has($name) { return isset($this->commandMap[$name]) && $this->container->has($this->commandMap[$name]); } public function getNames() { return array_keys($this->commandMap); } } factories = $factories; } public function has($name) { return isset($this->factories[$name]); } public function get($name) { if (!isset($this->factories[$name])) { throw new CommandNotFoundException(sprintf('Command "%s" does not exist.', $name)); } $factory = $this->factories[$name]; return $factory(); } public function getNames() { return array_keys($this->factories); } } commandShouldRun = false; } public function enableCommand() { return $this->commandShouldRun = true; } public function commandShouldRun() { return $this->commandShouldRun; } } setException($exception); $this->exitCode = (int) $exitCode; } public function getException() { return $this->exception; } public function setException(\Exception $exception) { $this->exception = $exception; } public function getExitCode() { return $this->exitCode; } } command = $command; $this->input = $input; $this->output = $output; } public function getCommand() { return $this->command; } public function getInput() { return $this->input; } public function getOutput() { return $this->output; } } setError($error); } public function getError() { return $this->error; } public function setError($error) { if (!$error instanceof \Throwable && !$error instanceof \Exception) { throw new InvalidArgumentException(sprintf('The error passed to ConsoleErrorEvent must be an instance of \Throwable or \Exception, "%s" was passed instead.', \is_object($error) ? \get_class($error) : \gettype($error))); } $this->error = $error; } public function setExitCode($exitCode) { $this->exitCode = (int) $exitCode; $r = new \ReflectionProperty($this->error, 'code'); $r->setAccessible(true); $r->setValue($this->error, $this->exitCode); } public function getExitCode() { return null !== $this->exitCode ? $this->exitCode : (\is_int($this->error->getCode()) && 0 !== $this->error->getCode() ? $this->error->getCode() : 1); } } setExitCode($exitCode); } public function setExitCode($exitCode) { $this->exitCode = (int) $exitCode; } public function getExitCode() { return $this->exitCode; } } = $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; } 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; } 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) { return strrchr($haystack, $needle, $part); } $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; } } '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' => 'k', 'Å' => 'å', 'Ⅎ' => 'ⅎ', 'Ⅰ' => 'ⅰ', 'Ⅱ' => 'ⅱ', 'Ⅲ' => 'ⅲ', 'Ⅳ' => 'ⅳ', 'Ⅴ' => 'ⅴ', 'Ⅵ' => 'ⅵ', 'Ⅶ' => 'ⅶ', 'Ⅷ' => 'ⅷ', 'Ⅸ' => 'ⅸ', 'Ⅹ' => 'ⅹ', 'Ⅺ' => 'ⅺ', 'Ⅻ' => 'ⅻ', 'Ⅼ' => 'ⅼ', 'Ⅽ' => 'ⅽ', 'Ⅾ' => 'ⅾ', 'Ⅿ' => 'ⅿ', 'Ↄ' => 'ↄ', 'Ⓐ' => 'ⓐ', 'Ⓑ' => 'ⓑ', 'Ⓒ' => 'ⓒ', 'Ⓓ' => 'ⓓ', 'Ⓔ' => 'ⓔ', 'Ⓕ' => 'ⓕ', 'Ⓖ' => 'ⓖ', 'Ⓗ' => 'ⓗ', 'Ⓘ' => 'ⓘ', 'Ⓙ' => 'ⓙ', 'Ⓚ' => 'ⓚ', 'Ⓛ' => 'ⓛ', 'Ⓜ' => 'ⓜ', 'Ⓝ' => 'ⓝ', 'Ⓞ' => 'ⓞ', 'Ⓟ' => 'ⓟ', 'Ⓠ' => 'ⓠ', 'Ⓡ' => 'ⓡ', 'Ⓢ' => 'ⓢ', 'Ⓣ' => 'ⓣ', 'Ⓤ' => 'ⓤ', 'Ⓥ' => 'ⓥ', 'Ⓦ' => 'ⓦ', 'Ⓧ' => 'ⓧ', 'Ⓨ' => 'ⓨ', 'Ⓩ' => 'ⓩ', 'Ⰰ' => 'ⰰ', 'Ⰱ' => 'ⰱ', 'Ⰲ' => 'ⰲ', 'Ⰳ' => 'ⰳ', 'Ⰴ' => 'ⰴ', 'Ⰵ' => 'ⰵ', 'Ⰶ' => 'ⰶ', 'Ⰷ' => 'ⰷ', 'Ⰸ' => 'ⰸ', 'Ⰹ' => 'ⰹ', 'Ⰺ' => 'ⰺ', 'Ⰻ' => 'ⰻ', 'Ⰼ' => 'ⰼ', 'Ⰽ' => 'ⰽ', 'Ⰾ' => 'ⰾ', 'Ⰿ' => 'ⰿ', 'Ⱀ' => 'ⱀ', 'Ⱁ' => 'ⱁ', 'Ⱂ' => 'ⱂ', 'Ⱃ' => 'ⱃ', 'Ⱄ' => 'ⱄ', 'Ⱅ' => 'ⱅ', 'Ⱆ' => 'ⱆ', 'Ⱇ' => 'ⱇ', 'Ⱈ' => 'ⱈ', 'Ⱉ' => 'ⱉ', 'Ⱊ' => 'ⱊ', 'Ⱋ' => 'ⱋ', 'Ⱌ' => 'ⱌ', 'Ⱍ' => 'ⱍ', 'Ⱎ' => 'ⱎ', 'Ⱏ' => 'ⱏ', 'Ⱐ' => 'ⱐ', 'Ⱑ' => 'ⱑ', 'Ⱒ' => 'ⱒ', 'Ⱓ' => 'ⱓ', 'Ⱔ' => 'ⱔ', 'Ⱕ' => 'ⱕ', 'Ⱖ' => 'ⱖ', 'Ⱗ' => 'ⱗ', 'Ⱘ' => 'ⱘ', 'Ⱙ' => 'ⱙ', 'Ⱚ' => 'ⱚ', 'Ⱛ' => 'ⱛ', 'Ⱜ' => 'ⱜ', 'Ⱝ' => 'ⱝ', 'Ⱞ' => 'ⱞ', 'Ⱡ' => 'ⱡ', 'Ɫ' => 'ɫ', 'Ᵽ' => 'ᵽ', 'Ɽ' => 'ɽ', 'Ⱨ' => 'ⱨ', 'Ⱪ' => 'ⱪ', 'Ⱬ' => 'ⱬ', 'Ɑ' => 'ɑ', 'Ɱ' => 'ɱ', 'Ɐ' => 'ɐ', 'Ɒ' => 'ɒ', 'Ⱳ' => 'ⱳ', 'Ⱶ' => 'ⱶ', 'Ȿ' => 'ȿ', 'Ɀ' => 'ɀ', 'Ⲁ' => 'ⲁ', 'Ⲃ' => 'ⲃ', 'Ⲅ' => 'ⲅ', 'Ⲇ' => 'ⲇ', 'Ⲉ' => 'ⲉ', 'Ⲋ' => 'ⲋ', 'Ⲍ' => 'ⲍ', 'Ⲏ' => 'ⲏ', 'Ⲑ' => 'ⲑ', 'Ⲓ' => 'ⲓ', 'Ⲕ' => 'ⲕ', 'Ⲗ' => 'ⲗ', 'Ⲙ' => 'ⲙ', 'Ⲛ' => 'ⲛ', 'Ⲝ' => 'ⲝ', 'Ⲟ' => 'ⲟ', 'Ⲡ' => 'ⲡ', 'Ⲣ' => 'ⲣ', 'Ⲥ' => 'ⲥ', 'Ⲧ' => 'ⲧ', 'Ⲩ' => 'ⲩ', 'Ⲫ' => 'ⲫ', 'Ⲭ' => 'ⲭ', 'Ⲯ' => 'ⲯ', 'Ⲱ' => 'ⲱ', 'Ⲳ' => 'ⲳ', 'Ⲵ' => 'ⲵ', 'Ⲷ' => 'ⲷ', 'Ⲹ' => 'ⲹ', 'Ⲻ' => 'ⲻ', 'Ⲽ' => 'ⲽ', 'Ⲿ' => 'ⲿ', 'Ⳁ' => 'ⳁ', 'Ⳃ' => 'ⳃ', 'Ⳅ' => 'ⳅ', 'Ⳇ' => 'ⳇ', 'Ⳉ' => 'ⳉ', 'Ⳋ' => 'ⳋ', 'Ⳍ' => 'ⳍ', 'Ⳏ' => 'ⳏ', 'Ⳑ' => 'ⳑ', 'Ⳓ' => 'ⳓ', 'Ⳕ' => 'ⳕ', 'Ⳗ' => 'ⳗ', 'Ⳙ' => 'ⳙ', 'Ⳛ' => 'ⳛ', 'Ⳝ' => 'ⳝ', 'Ⳟ' => 'ⳟ', 'Ⳡ' => 'ⳡ', 'Ⳣ' => 'ⳣ', 'Ⳬ' => 'ⳬ', 'Ⳮ' => 'ⳮ', 'Ⳳ' => 'ⳳ', 'Ꙁ' => 'ꙁ', 'Ꙃ' => 'ꙃ', 'Ꙅ' => 'ꙅ', 'Ꙇ' => 'ꙇ', 'Ꙉ' => 'ꙉ', 'Ꙋ' => 'ꙋ', 'Ꙍ' => 'ꙍ', 'Ꙏ' => 'ꙏ', 'Ꙑ' => 'ꙑ', 'Ꙓ' => 'ꙓ', 'Ꙕ' => 'ꙕ', 'Ꙗ' => 'ꙗ', 'Ꙙ' => 'ꙙ', 'Ꙛ' => 'ꙛ', 'Ꙝ' => 'ꙝ', 'Ꙟ' => 'ꙟ', 'Ꙡ' => 'ꙡ', 'Ꙣ' => 'ꙣ', 'Ꙥ' => 'ꙥ', 'Ꙧ' => 'ꙧ', 'Ꙩ' => 'ꙩ', 'Ꙫ' => 'ꙫ', 'Ꙭ' => 'ꙭ', 'Ꚁ' => 'ꚁ', 'Ꚃ' => 'ꚃ', 'Ꚅ' => 'ꚅ', 'Ꚇ' => 'ꚇ', 'Ꚉ' => 'ꚉ', 'Ꚋ' => 'ꚋ', 'Ꚍ' => 'ꚍ', 'Ꚏ' => 'ꚏ', 'Ꚑ' => 'ꚑ', 'Ꚓ' => 'ꚓ', 'Ꚕ' => 'ꚕ', 'Ꚗ' => 'ꚗ', 'Ꚙ' => 'ꚙ', 'Ꚛ' => 'ꚛ', 'Ꜣ' => 'ꜣ', 'Ꜥ' => 'ꜥ', 'Ꜧ' => 'ꜧ', 'Ꜩ' => 'ꜩ', 'Ꜫ' => 'ꜫ', 'Ꜭ' => 'ꜭ', 'Ꜯ' => 'ꜯ', 'Ꜳ' => 'ꜳ', 'Ꜵ' => 'ꜵ', 'Ꜷ' => 'ꜷ', 'Ꜹ' => 'ꜹ', 'Ꜻ' => 'ꜻ', 'Ꜽ' => 'ꜽ', 'Ꜿ' => 'ꜿ', 'Ꝁ' => 'ꝁ', 'Ꝃ' => 'ꝃ', 'Ꝅ' => 'ꝅ', 'Ꝇ' => 'ꝇ', 'Ꝉ' => 'ꝉ', 'Ꝋ' => 'ꝋ', 'Ꝍ' => 'ꝍ', 'Ꝏ' => 'ꝏ', 'Ꝑ' => 'ꝑ', 'Ꝓ' => 'ꝓ', 'Ꝕ' => 'ꝕ', 'Ꝗ' => 'ꝗ', 'Ꝙ' => 'ꝙ', 'Ꝛ' => 'ꝛ', 'Ꝝ' => 'ꝝ', 'Ꝟ' => 'ꝟ', 'Ꝡ' => 'ꝡ', 'Ꝣ' => 'ꝣ', 'Ꝥ' => 'ꝥ', 'Ꝧ' => 'ꝧ', 'Ꝩ' => 'ꝩ', 'Ꝫ' => 'ꝫ', 'Ꝭ' => 'ꝭ', 'Ꝯ' => 'ꝯ', 'Ꝺ' => 'ꝺ', 'Ꝼ' => 'ꝼ', 'Ᵹ' => 'ᵹ', 'Ꝿ' => 'ꝿ', 'Ꞁ' => 'ꞁ', 'Ꞃ' => 'ꞃ', 'Ꞅ' => 'ꞅ', 'Ꞇ' => 'ꞇ', 'Ꞌ' => 'ꞌ', 'Ɥ' => 'ɥ', 'Ꞑ' => 'ꞑ', 'Ꞓ' => 'ꞓ', 'Ꞗ' => 'ꞗ', 'Ꞙ' => 'ꞙ', 'Ꞛ' => 'ꞛ', 'Ꞝ' => 'ꞝ', 'Ꞟ' => 'ꞟ', 'Ꞡ' => 'ꞡ', 'Ꞣ' => 'ꞣ', 'Ꞥ' => 'ꞥ', 'Ꞧ' => 'ꞧ', 'Ꞩ' => 'ꞩ', 'Ɦ' => 'ɦ', 'Ɜ' => 'ɜ', 'Ɡ' => 'ɡ', 'Ɬ' => 'ɬ', 'Ʞ' => 'ʞ', 'Ʇ' => 'ʇ', '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', '𐐀' => '𐐨', '𐐁' => '𐐩', '𐐂' => '𐐪', '𐐃' => '𐐫', '𐐄' => '𐐬', '𐐅' => '𐐭', '𐐆' => '𐐮', '𐐇' => '𐐯', '𐐈' => '𐐰', '𐐉' => '𐐱', '𐐊' => '𐐲', '𐐋' => '𐐳', '𐐌' => '𐐴', '𐐍' => '𐐵', '𐐎' => '𐐶', '𐐏' => '𐐷', '𐐐' => '𐐸', '𐐑' => '𐐹', '𐐒' => '𐐺', '𐐓' => '𐐻', '𐐔' => '𐐼', '𐐕' => '𐐽', '𐐖' => '𐐾', '𐐗' => '𐐿', '𐐘' => '𐑀', '𐐙' => '𐑁', '𐐚' => '𐑂', '𐐛' => '𐑃', '𐐜' => '𐑄', '𐐝' => '𐑅', '𐐞' => '𐑆', '𐐟' => '𐑇', '𐐠' => '𐑈', '𐐡' => '𐑉', '𐐢' => '𐑊', '𐐣' => '𐑋', '𐐤' => '𐑌', '𐐥' => '𐑍', '𐐦' => '𐑎', '𐐧' => '𐑏', '𑢠' => '𑣀', '𑢡' => '𑣁', '𑢢' => '𑣂', '𑢣' => '𑣃', '𑢤' => '𑣄', '𑢥' => '𑣅', '𑢦' => '𑣆', '𑢧' => '𑣇', '𑢨' => '𑣈', '𑢩' => '𑣉', '𑢪' => '𑣊', '𑢫' => '𑣋', '𑢬' => '𑣌', '𑢭' => '𑣍', '𑢮' => '𑣎', '𑢯' => '𑣏', '𑢰' => '𑣐', '𑢱' => '𑣑', '𑢲' => '𑣒', '𑢳' => '𑣓', '𑢴' => '𑣔', '𑢵' => '𑣕', '𑢶' => '𑣖', '𑢷' => '𑣗', '𑢸' => '𑣘', '𑢹' => '𑣙', '𑢺' => '𑣚', '𑢻' => '𑣛', '𑢼' => '𑣜', '𑢽' => '𑣝', '𑢾' => '𑣞', '𑢿' => '𑣟', ); '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', 'ǵ' => 'Ǵ', 'ǹ' => 'Ǹ', 'ǻ' => 'Ǻ', 'ǽ' => 'Ǽ', 'ǿ' => 'Ǿ', 'ȁ' => 'Ȁ', 'ȃ' => 'Ȃ', 'ȅ' => 'Ȅ', 'ȇ' => 'Ȇ', 'ȉ' => 'Ȉ', 'ȋ' => 'Ȋ', 'ȍ' => 'Ȍ', 'ȏ' => 'Ȏ', 'ȑ' => 'Ȑ', 'ȓ' => 'Ȓ', 'ȕ' => 'Ȕ', 'ȗ' => 'Ȗ', 'ș' => 'Ș', 'ț' => 'Ț', 'ȝ' => 'Ȝ', 'ȟ' => 'Ȟ', 'ȣ' => 'Ȣ', 'ȥ' => 'Ȥ', 'ȧ' => 'Ȧ', 'ȩ' => 'Ȩ', 'ȫ' => 'Ȫ', 'ȭ' => 'Ȭ', 'ȯ' => 'Ȯ', 'ȱ' => 'Ȱ', 'ȳ' => 'Ȳ', 'ȼ' => 'Ȼ', 'ȿ' => 'Ȿ', 'ɀ' => 'Ɀ', 'ɂ' => 'Ɂ', 'ɇ' => 'Ɇ', 'ɉ' => 'Ɉ', 'ɋ' => 'Ɋ', 'ɍ' => 'Ɍ', 'ɏ' => 'Ɏ', 'ɐ' => 'Ɐ', 'ɑ' => 'Ɑ', 'ɒ' => 'Ɒ', 'ɓ' => 'Ɓ', 'ɔ' => 'Ɔ', 'ɖ' => 'Ɖ', 'ɗ' => 'Ɗ', 'ə' => 'Ə', 'ɛ' => 'Ɛ', 'ɜ' => 'Ɜ', 'ɠ' => 'Ɠ', 'ɡ' => 'Ɡ', 'ɣ' => 'Ɣ', 'ɥ' => 'Ɥ', 'ɦ' => 'Ɦ', 'ɨ' => 'Ɨ', 'ɩ' => 'Ɩ', 'ɫ' => 'Ɫ', 'ɬ' => 'Ɬ', 'ɯ' => 'Ɯ', 'ɱ' => 'Ɱ', 'ɲ' => 'Ɲ', 'ɵ' => 'Ɵ', 'ɽ' => 'Ɽ', 'ʀ' => 'Ʀ', 'ʃ' => 'Ʃ', 'ʇ' => 'Ʇ', 'ʈ' => 'Ʈ', 'ʉ' => 'Ʉ', 'ʊ' => 'Ʊ', 'ʋ' => 'Ʋ', 'ʌ' => 'Ʌ', 'ʒ' => 'Ʒ', 'ʞ' => 'Ʞ', 'ͅ' => 'Ι', 'ͱ' => 'Ͱ', 'ͳ' => 'Ͳ', 'ͷ' => 'Ͷ', 'ͻ' => 'Ͻ', 'ͼ' => 'Ͼ', 'ͽ' => 'Ͽ', 'ά' => 'Ά', 'έ' => 'Έ', 'ή' => 'Ή', 'ί' => 'Ί', 'α' => 'Α', 'β' => 'Β', 'γ' => 'Γ', 'δ' => 'Δ', 'ε' => 'Ε', 'ζ' => 'Ζ', 'η' => 'Η', 'θ' => 'Θ', 'ι' => 'Ι', 'κ' => 'Κ', 'λ' => 'Λ', 'μ' => 'Μ', 'ν' => 'Ν', 'ξ' => 'Ξ', 'ο' => 'Ο', 'π' => 'Π', 'ρ' => 'Ρ', 'ς' => 'Σ', 'σ' => 'Σ', 'τ' => 'Τ', 'υ' => 'Υ', 'φ' => 'Φ', 'χ' => 'Χ', 'ψ' => 'Ψ', 'ω' => 'Ω', 'ϊ' => 'Ϊ', 'ϋ' => 'Ϋ', 'ό' => 'Ό', 'ύ' => 'Ύ', 'ώ' => 'Ώ', 'ϐ' => 'Β', 'ϑ' => 'Θ', 'ϕ' => 'Φ', 'ϖ' => 'Π', 'ϗ' => 'Ϗ', 'ϙ' => 'Ϙ', 'ϛ' => 'Ϛ', 'ϝ' => 'Ϝ', 'ϟ' => 'Ϟ', 'ϡ' => 'Ϡ', 'ϣ' => 'Ϣ', 'ϥ' => 'Ϥ', 'ϧ' => 'Ϧ', 'ϩ' => 'Ϩ', 'ϫ' => 'Ϫ', 'ϭ' => 'Ϭ', 'ϯ' => 'Ϯ', 'ϰ' => 'Κ', 'ϱ' => 'Ρ', 'ϲ' => 'Ϲ', 'ϳ' => 'Ϳ', 'ϵ' => 'Ε', 'ϸ' => 'Ϸ', 'ϻ' => 'Ϻ', 'а' => 'А', 'б' => 'Б', 'в' => 'В', 'г' => 'Г', 'д' => 'Д', 'е' => 'Е', 'ж' => 'Ж', 'з' => 'З', 'и' => 'И', 'й' => 'Й', 'к' => 'К', 'л' => 'Л', 'м' => 'М', 'н' => 'Н', 'о' => 'О', 'п' => 'П', 'р' => 'Р', 'с' => 'С', 'т' => 'Т', 'у' => 'У', 'ф' => 'Ф', 'х' => 'Х', 'ц' => 'Ц', 'ч' => 'Ч', 'ш' => 'Ш', 'щ' => 'Щ', 'ъ' => 'Ъ', 'ы' => 'Ы', 'ь' => 'Ь', 'э' => 'Э', 'ю' => 'Ю', 'я' => 'Я', 'ѐ' => 'Ѐ', 'ё' => 'Ё', 'ђ' => 'Ђ', 'ѓ' => 'Ѓ', 'є' => 'Є', 'ѕ' => 'Ѕ', 'і' => 'І', 'ї' => 'Ї', 'ј' => 'Ј', 'љ' => 'Љ', 'њ' => 'Њ', 'ћ' => 'Ћ', 'ќ' => 'Ќ', 'ѝ' => 'Ѝ', 'ў' => 'Ў', 'џ' => 'Џ', 'ѡ' => 'Ѡ', 'ѣ' => 'Ѣ', 'ѥ' => 'Ѥ', 'ѧ' => 'Ѧ', 'ѩ' => 'Ѩ', 'ѫ' => 'Ѫ', 'ѭ' => 'Ѭ', 'ѯ' => 'Ѯ', 'ѱ' => 'Ѱ', 'ѳ' => 'Ѳ', 'ѵ' => 'Ѵ', 'ѷ' => 'Ѷ', 'ѹ' => 'Ѹ', 'ѻ' => 'Ѻ', 'ѽ' => 'Ѽ', 'ѿ' => 'Ѿ', 'ҁ' => 'Ҁ', 'ҋ' => 'Ҋ', 'ҍ' => 'Ҍ', 'ҏ' => 'Ҏ', 'ґ' => 'Ґ', 'ғ' => 'Ғ', 'ҕ' => 'Ҕ', 'җ' => 'Җ', 'ҙ' => 'Ҙ', 'қ' => 'Қ', 'ҝ' => 'Ҝ', 'ҟ' => 'Ҟ', 'ҡ' => 'Ҡ', 'ң' => 'Ң', 'ҥ' => 'Ҥ', 'ҧ' => 'Ҧ', 'ҩ' => 'Ҩ', 'ҫ' => 'Ҫ', 'ҭ' => 'Ҭ', 'ү' => 'Ү', 'ұ' => 'Ұ', 'ҳ' => 'Ҳ', 'ҵ' => 'Ҵ', 'ҷ' => 'Ҷ', 'ҹ' => 'Ҹ', 'һ' => 'Һ', 'ҽ' => 'Ҽ', 'ҿ' => 'Ҿ', 'ӂ' => 'Ӂ', 'ӄ' => 'Ӄ', 'ӆ' => 'Ӆ', 'ӈ' => 'Ӈ', 'ӊ' => 'Ӊ', 'ӌ' => 'Ӌ', 'ӎ' => 'Ӎ', 'ӏ' => 'Ӏ', 'ӑ' => 'Ӑ', 'ӓ' => 'Ӓ', 'ӕ' => 'Ӕ', 'ӗ' => 'Ӗ', 'ә' => 'Ә', 'ӛ' => 'Ӛ', 'ӝ' => 'Ӝ', 'ӟ' => 'Ӟ', 'ӡ' => 'Ӡ', 'ӣ' => 'Ӣ', 'ӥ' => 'Ӥ', 'ӧ' => 'Ӧ', 'ө' => 'Ө', 'ӫ' => 'Ӫ', 'ӭ' => 'Ӭ', 'ӯ' => 'Ӯ', 'ӱ' => 'Ӱ', 'ӳ' => 'Ӳ', 'ӵ' => 'Ӵ', 'ӷ' => 'Ӷ', 'ӹ' => 'Ӹ', 'ӻ' => 'Ӻ', 'ӽ' => 'Ӽ', 'ӿ' => 'Ӿ', 'ԁ' => 'Ԁ', 'ԃ' => 'Ԃ', 'ԅ' => 'Ԅ', 'ԇ' => 'Ԇ', 'ԉ' => 'Ԉ', 'ԋ' => 'Ԋ', 'ԍ' => 'Ԍ', 'ԏ' => 'Ԏ', 'ԑ' => 'Ԑ', 'ԓ' => 'Ԓ', 'ԕ' => 'Ԕ', 'ԗ' => 'Ԗ', 'ԙ' => 'Ԙ', 'ԛ' => 'Ԛ', 'ԝ' => 'Ԝ', 'ԟ' => 'Ԟ', 'ԡ' => 'Ԡ', 'ԣ' => 'Ԣ', 'ԥ' => 'Ԥ', 'ԧ' => 'Ԧ', 'ԩ' => 'Ԩ', 'ԫ' => 'Ԫ', 'ԭ' => 'Ԭ', 'ԯ' => 'Ԯ', 'ա' => 'Ա', 'բ' => 'Բ', 'գ' => 'Գ', 'դ' => 'Դ', 'ե' => 'Ե', 'զ' => 'Զ', 'է' => 'Է', 'ը' => 'Ը', 'թ' => 'Թ', 'ժ' => 'Ժ', 'ի' => 'Ի', 'լ' => 'Լ', 'խ' => 'Խ', 'ծ' => 'Ծ', 'կ' => 'Կ', 'հ' => 'Հ', 'ձ' => 'Ձ', 'ղ' => 'Ղ', 'ճ' => 'Ճ', 'մ' => 'Մ', 'յ' => 'Յ', 'ն' => 'Ն', 'շ' => 'Շ', 'ո' => 'Ո', 'չ' => 'Չ', 'պ' => 'Պ', 'ջ' => 'Ջ', 'ռ' => 'Ռ', 'ս' => 'Ս', 'վ' => 'Վ', 'տ' => 'Տ', 'ր' => 'Ր', 'ց' => 'Ց', 'ւ' => 'Ւ', 'փ' => 'Փ', 'ք' => 'Ք', 'օ' => 'Օ', 'ֆ' => 'Ֆ', 'ᵹ' => 'Ᵹ', 'ᵽ' => 'Ᵽ', 'ḁ' => 'Ḁ', 'ḃ' => 'Ḃ', 'ḅ' => 'Ḅ', 'ḇ' => 'Ḇ', 'ḉ' => 'Ḉ', 'ḋ' => 'Ḋ', 'ḍ' => 'Ḍ', 'ḏ' => 'Ḏ', 'ḑ' => 'Ḑ', 'ḓ' => 'Ḓ', 'ḕ' => 'Ḕ', 'ḗ' => 'Ḗ', 'ḙ' => 'Ḙ', 'ḛ' => 'Ḛ', 'ḝ' => 'Ḝ', 'ḟ' => 'Ḟ', 'ḡ' => 'Ḡ', 'ḣ' => 'Ḣ', 'ḥ' => 'Ḥ', 'ḧ' => 'Ḧ', 'ḩ' => 'Ḩ', 'ḫ' => 'Ḫ', 'ḭ' => 'Ḭ', 'ḯ' => 'Ḯ', 'ḱ' => 'Ḱ', 'ḳ' => 'Ḳ', 'ḵ' => 'Ḵ', 'ḷ' => 'Ḷ', 'ḹ' => 'Ḹ', 'ḻ' => 'Ḻ', 'ḽ' => 'Ḽ', 'ḿ' => 'Ḿ', 'ṁ' => 'Ṁ', 'ṃ' => 'Ṃ', 'ṅ' => 'Ṅ', 'ṇ' => 'Ṇ', 'ṉ' => 'Ṉ', 'ṋ' => 'Ṋ', 'ṍ' => 'Ṍ', 'ṏ' => 'Ṏ', 'ṑ' => 'Ṑ', 'ṓ' => 'Ṓ', 'ṕ' => 'Ṕ', 'ṗ' => 'Ṗ', 'ṙ' => 'Ṙ', 'ṛ' => 'Ṛ', 'ṝ' => 'Ṝ', 'ṟ' => 'Ṟ', 'ṡ' => 'Ṡ', 'ṣ' => 'Ṣ', 'ṥ' => 'Ṥ', 'ṧ' => 'Ṧ', 'ṩ' => 'Ṩ', 'ṫ' => 'Ṫ', 'ṭ' => 'Ṭ', 'ṯ' => 'Ṯ', 'ṱ' => 'Ṱ', 'ṳ' => 'Ṳ', 'ṵ' => 'Ṵ', 'ṷ' => 'Ṷ', 'ṹ' => 'Ṹ', 'ṻ' => 'Ṻ', 'ṽ' => 'Ṽ', 'ṿ' => 'Ṿ', 'ẁ' => 'Ẁ', 'ẃ' => 'Ẃ', 'ẅ' => 'Ẅ', 'ẇ' => 'Ẇ', 'ẉ' => 'Ẉ', 'ẋ' => 'Ẋ', 'ẍ' => 'Ẍ', 'ẏ' => 'Ẏ', 'ẑ' => 'Ẑ', 'ẓ' => 'Ẓ', 'ẕ' => 'Ẕ', 'ẛ' => 'Ṡ', 'ạ' => 'Ạ', 'ả' => 'Ả', 'ấ' => 'Ấ', 'ầ' => 'Ầ', 'ẩ' => 'Ẩ', 'ẫ' => 'Ẫ', 'ậ' => 'Ậ', 'ắ' => 'Ắ', 'ằ' => 'Ằ', 'ẳ' => 'Ẳ', 'ẵ' => 'Ẵ', 'ặ' => 'Ặ', 'ẹ' => 'Ẹ', 'ẻ' => 'Ẻ', 'ẽ' => 'Ẽ', 'ế' => 'Ế', 'ề' => 'Ề', 'ể' => 'Ể', 'ễ' => 'Ễ', 'ệ' => 'Ệ', 'ỉ' => 'Ỉ', 'ị' => 'Ị', 'ọ' => 'Ọ', 'ỏ' => 'Ỏ', 'ố' => 'Ố', 'ồ' => 'Ồ', 'ổ' => 'Ổ', 'ỗ' => 'Ỗ', 'ộ' => 'Ộ', 'ớ' => 'Ớ', 'ờ' => 'Ờ', 'ở' => 'Ở', 'ỡ' => 'Ỡ', 'ợ' => 'Ợ', 'ụ' => 'Ụ', 'ủ' => 'Ủ', 'ứ' => 'Ứ', 'ừ' => 'Ừ', 'ử' => 'Ử', 'ữ' => 'Ữ', 'ự' => 'Ự', 'ỳ' => 'Ỳ', 'ỵ' => 'Ỵ', 'ỷ' => 'Ỷ', 'ỹ' => 'Ỹ', 'ỻ' => 'Ỻ', 'ỽ' => 'Ỽ', 'ỿ' => 'Ỿ', 'ἀ' => 'Ἀ', 'ἁ' => 'Ἁ', 'ἂ' => 'Ἂ', 'ἃ' => 'Ἃ', 'ἄ' => 'Ἄ', 'ἅ' => 'Ἅ', 'ἆ' => 'Ἆ', 'ἇ' => 'Ἇ', 'ἐ' => 'Ἐ', 'ἑ' => 'Ἑ', 'ἒ' => 'Ἒ', 'ἓ' => 'Ἓ', 'ἔ' => 'Ἔ', 'ἕ' => 'Ἕ', 'ἠ' => 'Ἠ', 'ἡ' => 'Ἡ', 'ἢ' => 'Ἢ', 'ἣ' => 'Ἣ', 'ἤ' => 'Ἤ', 'ἥ' => 'Ἥ', 'ἦ' => 'Ἦ', 'ἧ' => 'Ἧ', 'ἰ' => 'Ἰ', 'ἱ' => 'Ἱ', 'ἲ' => 'Ἲ', 'ἳ' => 'Ἳ', 'ἴ' => 'Ἴ', 'ἵ' => 'Ἵ', 'ἶ' => 'Ἶ', 'ἷ' => 'Ἷ', 'ὀ' => 'Ὀ', 'ὁ' => 'Ὁ', 'ὂ' => 'Ὂ', 'ὃ' => 'Ὃ', 'ὄ' => 'Ὄ', 'ὅ' => 'Ὅ', 'ὑ' => 'Ὑ', 'ὓ' => 'Ὓ', 'ὕ' => 'Ὕ', 'ὗ' => 'Ὗ', 'ὠ' => 'Ὠ', 'ὡ' => 'Ὡ', 'ὢ' => 'Ὢ', 'ὣ' => 'Ὣ', 'ὤ' => 'Ὤ', 'ὥ' => 'Ὥ', 'ὦ' => 'Ὦ', 'ὧ' => 'Ὧ', 'ὰ' => 'Ὰ', 'ά' => 'Ά', 'ὲ' => 'Ὲ', 'έ' => 'Έ', 'ὴ' => 'Ὴ', 'ή' => 'Ή', 'ὶ' => 'Ὶ', 'ί' => 'Ί', 'ὸ' => 'Ὸ', 'ό' => 'Ό', 'ὺ' => 'Ὺ', 'ύ' => 'Ύ', 'ὼ' => 'Ὼ', 'ώ' => 'Ώ', 'ᾀ' => 'ᾈ', 'ᾁ' => 'ᾉ', 'ᾂ' => 'ᾊ', 'ᾃ' => 'ᾋ', 'ᾄ' => 'ᾌ', 'ᾅ' => 'ᾍ', 'ᾆ' => 'ᾎ', 'ᾇ' => 'ᾏ', 'ᾐ' => 'ᾘ', 'ᾑ' => 'ᾙ', 'ᾒ' => 'ᾚ', 'ᾓ' => 'ᾛ', 'ᾔ' => 'ᾜ', 'ᾕ' => 'ᾝ', 'ᾖ' => 'ᾞ', 'ᾗ' => 'ᾟ', 'ᾠ' => 'ᾨ', 'ᾡ' => 'ᾩ', 'ᾢ' => 'ᾪ', 'ᾣ' => 'ᾫ', 'ᾤ' => 'ᾬ', 'ᾥ' => 'ᾭ', 'ᾦ' => 'ᾮ', 'ᾧ' => 'ᾯ', 'ᾰ' => 'Ᾰ', 'ᾱ' => 'Ᾱ', 'ᾳ' => 'ᾼ', 'ι' => 'Ι', 'ῃ' => 'ῌ', 'ῐ' => 'Ῐ', 'ῑ' => 'Ῑ', 'ῠ' => 'Ῠ', 'ῡ' => 'Ῡ', 'ῥ' => 'Ῥ', 'ῳ' => 'ῼ', 'ⅎ' => 'Ⅎ', 'ⅰ' => 'Ⅰ', 'ⅱ' => 'Ⅱ', 'ⅲ' => 'Ⅲ', 'ⅳ' => 'Ⅳ', 'ⅴ' => 'Ⅴ', 'ⅵ' => 'Ⅵ', 'ⅶ' => 'Ⅶ', 'ⅷ' => 'Ⅷ', 'ⅸ' => 'Ⅸ', 'ⅹ' => 'Ⅹ', 'ⅺ' => 'Ⅺ', 'ⅻ' => 'Ⅻ', 'ⅼ' => 'Ⅼ', 'ⅽ' => 'Ⅽ', 'ⅾ' => 'Ⅾ', 'ⅿ' => 'Ⅿ', 'ↄ' => 'Ↄ', 'ⓐ' => 'Ⓐ', 'ⓑ' => 'Ⓑ', 'ⓒ' => 'Ⓒ', 'ⓓ' => 'Ⓓ', 'ⓔ' => 'Ⓔ', 'ⓕ' => 'Ⓕ', 'ⓖ' => 'Ⓖ', 'ⓗ' => 'Ⓗ', 'ⓘ' => 'Ⓘ', 'ⓙ' => 'Ⓙ', 'ⓚ' => 'Ⓚ', 'ⓛ' => 'Ⓛ', 'ⓜ' => 'Ⓜ', 'ⓝ' => 'Ⓝ', 'ⓞ' => 'Ⓞ', 'ⓟ' => 'Ⓟ', 'ⓠ' => 'Ⓠ', 'ⓡ' => 'Ⓡ', 'ⓢ' => 'Ⓢ', 'ⓣ' => 'Ⓣ', 'ⓤ' => 'Ⓤ', 'ⓥ' => 'Ⓥ', 'ⓦ' => 'Ⓦ', 'ⓧ' => 'Ⓧ', 'ⓨ' => 'Ⓨ', 'ⓩ' => 'Ⓩ', 'ⰰ' => 'Ⰰ', 'ⰱ' => 'Ⰱ', 'ⰲ' => 'Ⰲ', 'ⰳ' => 'Ⰳ', 'ⰴ' => 'Ⰴ', 'ⰵ' => 'Ⰵ', 'ⰶ' => 'Ⰶ', 'ⰷ' => 'Ⰷ', 'ⰸ' => 'Ⰸ', 'ⰹ' => 'Ⰹ', 'ⰺ' => 'Ⰺ', 'ⰻ' => 'Ⰻ', 'ⰼ' => 'Ⰼ', 'ⰽ' => 'Ⰽ', 'ⰾ' => 'Ⰾ', 'ⰿ' => 'Ⰿ', 'ⱀ' => 'Ⱀ', 'ⱁ' => 'Ⱁ', 'ⱂ' => 'Ⱂ', 'ⱃ' => 'Ⱃ', 'ⱄ' => 'Ⱄ', 'ⱅ' => 'Ⱅ', 'ⱆ' => 'Ⱆ', 'ⱇ' => 'Ⱇ', 'ⱈ' => 'Ⱈ', 'ⱉ' => 'Ⱉ', 'ⱊ' => 'Ⱊ', 'ⱋ' => 'Ⱋ', 'ⱌ' => 'Ⱌ', 'ⱍ' => 'Ⱍ', 'ⱎ' => 'Ⱎ', 'ⱏ' => 'Ⱏ', 'ⱐ' => 'Ⱐ', 'ⱑ' => 'Ⱑ', 'ⱒ' => 'Ⱒ', 'ⱓ' => 'Ⱓ', 'ⱔ' => 'Ⱔ', 'ⱕ' => 'Ⱕ', 'ⱖ' => 'Ⱖ', 'ⱗ' => 'Ⱗ', 'ⱘ' => 'Ⱘ', 'ⱙ' => 'Ⱙ', 'ⱚ' => 'Ⱚ', 'ⱛ' => 'Ⱛ', 'ⱜ' => 'Ⱜ', 'ⱝ' => 'Ⱝ', 'ⱞ' => 'Ⱞ', 'ⱡ' => 'Ⱡ', 'ⱥ' => 'Ⱥ', 'ⱦ' => 'Ⱦ', 'ⱨ' => 'Ⱨ', 'ⱪ' => 'Ⱪ', 'ⱬ' => 'Ⱬ', 'ⱳ' => 'Ⱳ', 'ⱶ' => 'Ⱶ', 'ⲁ' => 'Ⲁ', 'ⲃ' => 'Ⲃ', 'ⲅ' => 'Ⲅ', 'ⲇ' => 'Ⲇ', 'ⲉ' => 'Ⲉ', 'ⲋ' => 'Ⲋ', 'ⲍ' => 'Ⲍ', 'ⲏ' => 'Ⲏ', 'ⲑ' => 'Ⲑ', 'ⲓ' => 'Ⲓ', 'ⲕ' => 'Ⲕ', 'ⲗ' => 'Ⲗ', 'ⲙ' => 'Ⲙ', 'ⲛ' => 'Ⲛ', 'ⲝ' => 'Ⲝ', 'ⲟ' => 'Ⲟ', 'ⲡ' => 'Ⲡ', 'ⲣ' => 'Ⲣ', 'ⲥ' => 'Ⲥ', 'ⲧ' => 'Ⲧ', 'ⲩ' => 'Ⲩ', 'ⲫ' => 'Ⲫ', 'ⲭ' => 'Ⲭ', 'ⲯ' => 'Ⲯ', 'ⲱ' => 'Ⲱ', 'ⲳ' => 'Ⲳ', 'ⲵ' => 'Ⲵ', 'ⲷ' => 'Ⲷ', 'ⲹ' => 'Ⲹ', 'ⲻ' => 'Ⲻ', 'ⲽ' => 'Ⲽ', 'ⲿ' => 'Ⲿ', 'ⳁ' => 'Ⳁ', 'ⳃ' => 'Ⳃ', 'ⳅ' => 'Ⳅ', 'ⳇ' => 'Ⳇ', 'ⳉ' => 'Ⳉ', 'ⳋ' => 'Ⳋ', 'ⳍ' => 'Ⳍ', 'ⳏ' => 'Ⳏ', 'ⳑ' => 'Ⳑ', 'ⳓ' => 'Ⳓ', 'ⳕ' => 'Ⳕ', 'ⳗ' => 'Ⳗ', 'ⳙ' => 'Ⳙ', 'ⳛ' => 'Ⳛ', 'ⳝ' => 'Ⳝ', 'ⳟ' => 'Ⳟ', 'ⳡ' => 'Ⳡ', 'ⳣ' => 'Ⳣ', 'ⳬ' => 'Ⳬ', 'ⳮ' => 'Ⳮ', 'ⳳ' => 'Ⳳ', 'ⴀ' => 'Ⴀ', 'ⴁ' => 'Ⴁ', 'ⴂ' => 'Ⴂ', 'ⴃ' => 'Ⴃ', 'ⴄ' => 'Ⴄ', 'ⴅ' => 'Ⴅ', 'ⴆ' => 'Ⴆ', 'ⴇ' => 'Ⴇ', 'ⴈ' => 'Ⴈ', 'ⴉ' => 'Ⴉ', 'ⴊ' => 'Ⴊ', 'ⴋ' => 'Ⴋ', 'ⴌ' => 'Ⴌ', 'ⴍ' => 'Ⴍ', 'ⴎ' => 'Ⴎ', 'ⴏ' => 'Ⴏ', 'ⴐ' => 'Ⴐ', 'ⴑ' => 'Ⴑ', 'ⴒ' => 'Ⴒ', 'ⴓ' => 'Ⴓ', 'ⴔ' => 'Ⴔ', 'ⴕ' => 'Ⴕ', 'ⴖ' => 'Ⴖ', 'ⴗ' => 'Ⴗ', 'ⴘ' => 'Ⴘ', 'ⴙ' => 'Ⴙ', 'ⴚ' => 'Ⴚ', 'ⴛ' => 'Ⴛ', 'ⴜ' => 'Ⴜ', 'ⴝ' => 'Ⴝ', 'ⴞ' => 'Ⴞ', 'ⴟ' => 'Ⴟ', 'ⴠ' => 'Ⴠ', 'ⴡ' => 'Ⴡ', 'ⴢ' => 'Ⴢ', 'ⴣ' => 'Ⴣ', 'ⴤ' => 'Ⴤ', 'ⴥ' => 'Ⴥ', 'ⴧ' => 'Ⴧ', 'ⴭ' => 'Ⴭ', 'ꙁ' => 'Ꙁ', 'ꙃ' => 'Ꙃ', 'ꙅ' => 'Ꙅ', 'ꙇ' => 'Ꙇ', 'ꙉ' => 'Ꙉ', 'ꙋ' => 'Ꙋ', 'ꙍ' => 'Ꙍ', 'ꙏ' => 'Ꙏ', 'ꙑ' => 'Ꙑ', 'ꙓ' => 'Ꙓ', 'ꙕ' => 'Ꙕ', 'ꙗ' => 'Ꙗ', 'ꙙ' => 'Ꙙ', 'ꙛ' => 'Ꙛ', 'ꙝ' => 'Ꙝ', 'ꙟ' => 'Ꙟ', 'ꙡ' => 'Ꙡ', 'ꙣ' => 'Ꙣ', 'ꙥ' => 'Ꙥ', 'ꙧ' => 'Ꙧ', 'ꙩ' => 'Ꙩ', 'ꙫ' => 'Ꙫ', 'ꙭ' => 'Ꙭ', 'ꚁ' => 'Ꚁ', 'ꚃ' => 'Ꚃ', 'ꚅ' => 'Ꚅ', 'ꚇ' => 'Ꚇ', 'ꚉ' => 'Ꚉ', 'ꚋ' => 'Ꚋ', 'ꚍ' => 'Ꚍ', 'ꚏ' => 'Ꚏ', 'ꚑ' => 'Ꚑ', 'ꚓ' => 'Ꚓ', 'ꚕ' => 'Ꚕ', 'ꚗ' => 'Ꚗ', 'ꚙ' => 'Ꚙ', 'ꚛ' => 'Ꚛ', 'ꜣ' => 'Ꜣ', 'ꜥ' => 'Ꜥ', 'ꜧ' => 'Ꜧ', 'ꜩ' => 'Ꜩ', 'ꜫ' => 'Ꜫ', 'ꜭ' => 'Ꜭ', 'ꜯ' => 'Ꜯ', 'ꜳ' => 'Ꜳ', 'ꜵ' => 'Ꜵ', 'ꜷ' => 'Ꜷ', 'ꜹ' => 'Ꜹ', 'ꜻ' => 'Ꜻ', 'ꜽ' => 'Ꜽ', 'ꜿ' => 'Ꜿ', 'ꝁ' => 'Ꝁ', 'ꝃ' => 'Ꝃ', 'ꝅ' => 'Ꝅ', 'ꝇ' => 'Ꝇ', 'ꝉ' => 'Ꝉ', 'ꝋ' => 'Ꝋ', 'ꝍ' => 'Ꝍ', 'ꝏ' => 'Ꝏ', 'ꝑ' => 'Ꝑ', 'ꝓ' => 'Ꝓ', 'ꝕ' => 'Ꝕ', 'ꝗ' => 'Ꝗ', 'ꝙ' => 'Ꝙ', 'ꝛ' => 'Ꝛ', 'ꝝ' => 'Ꝝ', 'ꝟ' => 'Ꝟ', 'ꝡ' => 'Ꝡ', 'ꝣ' => 'Ꝣ', 'ꝥ' => 'Ꝥ', 'ꝧ' => 'Ꝧ', 'ꝩ' => 'Ꝩ', 'ꝫ' => 'Ꝫ', 'ꝭ' => 'Ꝭ', 'ꝯ' => 'Ꝯ', 'ꝺ' => 'Ꝺ', 'ꝼ' => 'Ꝼ', 'ꝿ' => 'Ꝿ', 'ꞁ' => 'Ꞁ', 'ꞃ' => 'Ꞃ', 'ꞅ' => 'Ꞅ', 'ꞇ' => 'Ꞇ', 'ꞌ' => 'Ꞌ', 'ꞑ' => 'Ꞑ', 'ꞓ' => 'Ꞓ', 'ꞗ' => 'Ꞗ', 'ꞙ' => 'Ꞙ', 'ꞛ' => 'Ꞛ', 'ꞝ' => 'Ꞝ', 'ꞟ' => 'Ꞟ', 'ꞡ' => 'Ꞡ', 'ꞣ' => 'Ꞣ', 'ꞥ' => 'Ꞥ', 'ꞧ' => 'Ꞧ', 'ꞩ' => 'Ꞩ', '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', '𐐨' => '𐐀', '𐐩' => '𐐁', '𐐪' => '𐐂', '𐐫' => '𐐃', '𐐬' => '𐐄', '𐐭' => '𐐅', '𐐮' => '𐐆', '𐐯' => '𐐇', '𐐰' => '𐐈', '𐐱' => '𐐉', '𐐲' => '𐐊', '𐐳' => '𐐋', '𐐴' => '𐐌', '𐐵' => '𐐍', '𐐶' => '𐐎', '𐐷' => '𐐏', '𐐸' => '𐐐', '𐐹' => '𐐑', '𐐺' => '𐐒', '𐐻' => '𐐓', '𐐼' => '𐐔', '𐐽' => '𐐕', '𐐾' => '𐐖', '𐐿' => '𐐗', '𐑀' => '𐐘', '𐑁' => '𐐙', '𐑂' => '𐐚', '𐑃' => '𐐛', '𐑄' => '𐐜', '𐑅' => '𐐝', '𐑆' => '𐐞', '𐑇' => '𐐟', '𐑈' => '𐐠', '𐑉' => '𐐡', '𐑊' => '𐐢', '𐑋' => '𐐣', '𐑌' => '𐐤', '𐑍' => '𐐥', '𐑎' => '𐐦', '𐑏' => '𐐧', '𑣀' => '𑢠', '𑣁' => '𑢡', '𑣂' => '𑢢', '𑣃' => '𑢣', '𑣄' => '𑢤', '𑣅' => '𑢥', '𑣆' => '𑢦', '𑣇' => '𑢧', '𑣈' => '𑢨', '𑣉' => '𑢩', '𑣊' => '𑢪', '𑣋' => '𑢫', '𑣌' => '𑢬', '𑣍' => '𑢭', '𑣎' => '𑢮', '𑣏' => '𑢯', '𑣐' => '𑢰', '𑣑' => '𑢱', '𑣒' => '𑢲', '𑣓' => '𑢳', '𑣔' => '𑢴', '𑣕' => '𑢵', '𑣖' => '𑢶', '𑣗' => '𑢷', '𑣘' => '𑢸', '𑣙' => '𑢹', '𑣚' => '𑢺', '𑣛' => '𑢻', '𑣜' => '𑢼', '𑣝' => '𑢽', '𑣞' => '𑢾', '𑣟' => '𑢿', ); > 1, $j = 0; $i < $len; ++$i, ++$j) { switch (true) { case $s[$i] < "\x80": $s[$j] = $s[$i]; break; case $s[$i] < "\xC0": $s[$j] = "\xC2"; $s[++$j] = $s[$i]; break; default: $s[$j] = "\xC3"; $s[++$j] = \chr(\ord($s[$i]) - 64); break; } } return substr($s, 0, $j); } public static function utf8_decode($s) { $s = (string) $s; $len = \strlen($s); for ($i = 0, $j = 0; $i < $len; ++$i, ++$j) { switch ($s[$i] & "\xF0") { case "\xC0": case "\xD0": $c = (\ord($s[$i] & "\x1F") << 6) | \ord($s[++$i] & "\x3F"); $s[$j] = $c < 256 ? \chr($c) : '?'; break; case "\xF0": ++$i; case "\xE0": $s[$j] = '?'; $i += 2; break; default: $s[$j] = $s[$i]; } } return substr($s, 0, $j); } public static function php_os_family() { if ('\\' === \DIRECTORY_SEPARATOR) { return 'Windows'; } $map = array( 'Darwin' => 'Darwin', 'DragonFly' => 'BSD', 'FreeBSD' => 'BSD', 'NetBSD' => 'BSD', 'OpenBSD' => 'BSD', 'Linux' => 'Linux', 'SunOS' => 'Solaris', ); return isset($map[PHP_OS]) ? $map[PHP_OS] : 'Unknown'; } public static function spl_object_id($object) { if (null === self::$hashMask) { self::initHashMask(); } if (null === $hash = spl_object_hash($object)) { return; } return self::$hashMask ^ hexdec(substr($hash, 16 - \PHP_INT_SIZE, \PHP_INT_SIZE)); } public static function sapi_windows_vt100_support($stream, $enable = null) { if (!\is_resource($stream)) { trigger_error('sapi_windows_vt100_support() expects parameter 1 to be resource, '.\gettype($stream).' given', E_USER_WARNING); return false; } $meta = stream_get_meta_data($stream); if ('STDIO' !== $meta['stream_type']) { trigger_error('sapi_windows_vt100_support() was not able to analyze the specified stream', E_USER_WARNING); return false; } if (false === $enable || !self::stream_isatty($stream)) { return false; } $meta = array_map('strtolower', $meta); $stdin = 'php://stdin' === $meta['uri'] || 'php://fd/0' === $meta['uri']; return !$stdin && (false !== getenv('ANSICON') || 'ON' === getenv('ConEmuANSI') || 'xterm' === getenv('TERM') || 'Hyper' === getenv('TERM_PROGRAM')); } public static function stream_isatty($stream) { if (!\is_resource($stream)) { trigger_error('stream_isatty() expects parameter 1 to be resource, '.\gettype($stream).' given', E_USER_WARNING); return false; } if ('\\' === \DIRECTORY_SEPARATOR) { $stat = @fstat($stream); return $stat ? 0020000 === ($stat['mode'] & 0170000) : false; } return \function_exists('posix_isatty') && @posix_isatty($stream); } private static function initHashMask() { $obj = (object) array(); self::$hashMask = -1; $obFuncs = array('ob_clean', 'ob_end_clean', 'ob_flush', 'ob_end_flush', 'ob_get_contents', 'ob_get_flush'); foreach (debug_backtrace(\PHP_VERSION_ID >= 50400 ? DEBUG_BACKTRACE_IGNORE_ARGS : false) as $frame) { if (isset($frame['function'][0]) && !isset($frame['class']) && 'o' === $frame['function'][0] && \in_array($frame['function'], $obFuncs)) { $frame['line'] = 0; break; } } if (!empty($frame['line'])) { ob_start(); debug_zval_dump($obj); self::$hashMask = (int) substr(ob_get_clean(), 17); } self::$hashMask ^= hexdec(substr(spl_object_hash($obj), 16 - \PHP_INT_SIZE, \PHP_INT_SIZE)); } 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) { $s = mb_convert_encoding($s, $encoding, 'UTF-8'); } return $s; } public static function mb_ord($s, $encoding = null) { if (null == $encoding) { $s = mb_convert_encoding($s, 'UTF-8'); } elseif ('UTF-8' !== $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; } } 0, Process::STDERR => 0, ]; private $haveReadSupport; public function __construct($input, $haveReadSupport) { $this->haveReadSupport = (bool) $haveReadSupport; if ($this->haveReadSupport) { $pipes = [ Process::STDOUT => Process::OUT, Process::STDERR => Process::ERR, ]; $tmpDir = sys_get_temp_dir(); $lastError = 'unknown reason'; set_error_handler(function ($type, $msg) use (&$lastError) { $lastError = $msg; }); for ($i = 0;; ++$i) { foreach ($pipes as $pipe => $name) { $file = sprintf('%s\\sf_proc_%02X.%s', $tmpDir, $i, $name); if (!$h = fopen($file.'.lock', 'w')) { restore_error_handler(); throw new RuntimeException(sprintf('A temporary file could not be opened to write the process output: %s.', $lastError)); } if (!flock($h, LOCK_EX | LOCK_NB)) { continue 2; } if (isset($this->lockHandles[$pipe])) { flock($this->lockHandles[$pipe], LOCK_UN); fclose($this->lockHandles[$pipe]); } $this->lockHandles[$pipe] = $h; if (!fclose(fopen($file, 'w')) || !$h = fopen($file, 'r')) { flock($this->lockHandles[$pipe], LOCK_UN); fclose($this->lockHandles[$pipe]); unset($this->lockHandles[$pipe]); continue 2; } $this->fileHandles[$pipe] = $h; $this->files[$pipe] = $file; } break; } restore_error_handler(); } parent::__construct($input); } public function __destruct() { $this->close(); } public function getDescriptors() { if (!$this->haveReadSupport) { $nullstream = fopen('NUL', 'c'); return [ ['pipe', 'r'], $nullstream, $nullstream, ]; } return [ ['pipe', 'r'], ['file', 'NUL', 'w'], ['file', 'NUL', 'w'], ]; } public function getFiles() { return $this->files; } public function readAndWrite($blocking, $close = false) { $this->unblock(); $w = $this->write(); $read = $r = $e = []; if ($blocking) { if ($w) { @stream_select($r, $w, $e, 0, Process::TIMEOUT_PRECISION * 1E6); } elseif ($this->fileHandles) { usleep(Process::TIMEOUT_PRECISION * 1E6); } } foreach ($this->fileHandles as $type => $fileHandle) { $data = stream_get_contents($fileHandle, -1, $this->readBytes[$type]); if (isset($data[0])) { $this->readBytes[$type] += \strlen($data); $read[$type] = $data; } if ($close) { ftruncate($fileHandle, 0); fclose($fileHandle); flock($this->lockHandles[$type], LOCK_UN); fclose($this->lockHandles[$type]); unset($this->fileHandles[$type], $this->lockHandles[$type]); } } return $read; } public function haveReadSupport() { return $this->haveReadSupport; } public function areOpen() { return $this->pipes && $this->fileHandles; } public function close() { parent::close(); foreach ($this->fileHandles as $type => $handle) { ftruncate($handle, 0); fclose($handle); flock($this->lockHandles[$type], LOCK_UN); fclose($this->lockHandles[$type]); } $this->fileHandles = $this->lockHandles = []; } } ttyMode = (bool) $ttyMode; $this->ptyMode = (bool) $ptyMode; $this->haveReadSupport = (bool) $haveReadSupport; parent::__construct($input); } public function __destruct() { $this->close(); } public function getDescriptors() { if (!$this->haveReadSupport) { $nullstream = fopen('/dev/null', 'c'); return [ ['pipe', 'r'], $nullstream, $nullstream, ]; } if ($this->ttyMode) { return [ ['file', '/dev/tty', 'r'], ['file', '/dev/tty', 'w'], ['file', '/dev/tty', 'w'], ]; } if ($this->ptyMode && Process::isPtySupported()) { return [ ['pty'], ['pty'], ['pty'], ]; } return [ ['pipe', 'r'], ['pipe', 'w'], ['pipe', 'w'], ]; } public function getFiles() { return []; } public function readAndWrite($blocking, $close = false) { $this->unblock(); $w = $this->write(); $read = $e = []; $r = $this->pipes; unset($r[0]); set_error_handler([$this, 'handleError']); if (($r || $w) && false === stream_select($r, $w, $e, 0, $blocking ? Process::TIMEOUT_PRECISION * 1E6 : 0)) { restore_error_handler(); if (!$this->hasSystemCallBeenInterrupted()) { $this->pipes = []; } return $read; } restore_error_handler(); foreach ($r as $pipe) { $read[$type = array_search($pipe, $this->pipes, true)] = ''; do { $data = fread($pipe, self::CHUNK_SIZE); $read[$type] .= $data; } while (isset($data[0]) && ($close || isset($data[self::CHUNK_SIZE - 1]))); if (!isset($read[$type][0])) { unset($read[$type]); } if ($close && feof($pipe)) { fclose($pipe); unset($this->pipes[$type]); } } return $read; } public function haveReadSupport() { return $this->haveReadSupport; } public function areOpen() { return (bool) $this->pipes; } } input = $input; } elseif (\is_string($input)) { $this->inputBuffer = $input; } else { $this->inputBuffer = (string) $input; } } public function close() { foreach ($this->pipes as $pipe) { fclose($pipe); } $this->pipes = []; } protected function hasSystemCallBeenInterrupted() { $lastError = $this->lastError; $this->lastError = null; return null !== $lastError && false !== stripos($lastError, 'interrupted system call'); } protected function unblock() { if (!$this->blocked) { return; } foreach ($this->pipes as $pipe) { stream_set_blocking($pipe, 0); } if (\is_resource($this->input)) { stream_set_blocking($this->input, 0); } $this->blocked = false; } protected function write() { if (!isset($this->pipes[0])) { return null; } $input = $this->input; if ($input instanceof \Iterator) { if (!$input->valid()) { $input = null; } elseif (\is_resource($input = $input->current())) { stream_set_blocking($input, 0); } elseif (!isset($this->inputBuffer[0])) { if (!\is_string($input)) { if (!is_scalar($input)) { throw new InvalidArgumentException(sprintf('"%s" yielded a value of type "%s", but only scalars and stream resources are supported.', \get_class($this->input), \gettype($input))); } $input = (string) $input; } $this->inputBuffer = $input; $this->input->next(); $input = null; } else { $input = null; } } $r = $e = []; $w = [$this->pipes[0]]; if (false === @stream_select($r, $w, $e, 0, 0)) { return null; } foreach ($w as $stdin) { if (isset($this->inputBuffer[0])) { $written = fwrite($stdin, $this->inputBuffer); $this->inputBuffer = substr($this->inputBuffer, $written); if (isset($this->inputBuffer[0])) { return [$this->pipes[0]]; } } if ($input) { for (;;) { $data = fread($input, self::CHUNK_SIZE); if (!isset($data[0])) { break; } $written = fwrite($stdin, $data); $data = substr($data, $written); if (isset($data[0])) { $this->inputBuffer = $data; return [$this->pipes[0]]; } } if (feof($input)) { if ($this->input instanceof \Iterator) { $this->input->next(); } else { $this->input = null; } } } } if (!isset($this->inputBuffer[0]) && !($this->input instanceof \Iterator ? $this->input->valid() : $this->input)) { $this->input = null; fclose($this->pipes[0]); unset($this->pipes[0]); } elseif (!$w) { return [$this->pipes[0]]; } return null; } public function handleError($type, $msg) { $this->lastError = $msg; } } getIterator($input::ITER_SKIP_ERR); } if ($input instanceof \Iterator) { return $input; } if ($input instanceof \Traversable) { return new \IteratorIterator($input); } throw new InvalidArgumentException(sprintf('"%s" only accepts strings, Traversable objects or stream resources.', $caller)); } return $input; } private static function isSurroundedBy($arg, $char) { return 2 < \strlen($arg) && $char === $arg[0] && $char === $arg[\strlen($arg) - 1]; } } suffixes = $suffixes; } public function addSuffix($suffix) { $this->suffixes[] = $suffix; } public function find($name, $default = null, array $extraDirs = []) { if (ini_get('open_basedir')) { $searchPath = array_merge(explode(PATH_SEPARATOR, ini_get('open_basedir')), $extraDirs); $dirs = []; foreach ($searchPath as $path) { if (@is_dir($path)) { $dirs[] = $path; } else { if (basename($path) == $name && @is_executable($path)) { return $path; } } } } else { $dirs = array_merge( explode(PATH_SEPARATOR, getenv('PATH') ?: getenv('Path')), $extraDirs ); } $suffixes = ['']; if ('\\' === \DIRECTORY_SEPARATOR) { $pathExt = getenv('PATHEXT'); $suffixes = array_merge($pathExt ? explode(PATH_SEPARATOR, $pathExt) : $this->suffixes, $suffixes); } foreach ($suffixes as $suffix) { foreach ($dirs as $dir) { if (@is_file($file = $dir.\DIRECTORY_SEPARATOR.$name.$suffix) && ('\\' === \DIRECTORY_SEPARATOR || @is_executable($file))) { return $file; } } } return $default; } } find(false)) { $php = null; } else { $php = array_merge([$php], $executableFinder->findArguments()); } if ('phpdbg' === \PHP_SAPI) { $file = tempnam(sys_get_temp_dir(), 'dbg'); file_put_contents($file, $script); register_shutdown_function('unlink', $file); $php[] = $file; $script = null; } if (null !== $options) { @trigger_error(sprintf('The $options parameter of the %s constructor is deprecated since Symfony 3.3 and will be removed in 4.0.', __CLASS__), E_USER_DEPRECATED); } parent::__construct($php, $cwd, $env, $script, $timeout, $options); } public function setPhpBinary($php) { $this->setCommandLine($php); } public function start(callable $callback = null) { if (null === $this->getCommandLine()) { throw new RuntimeException('Unable to find the PHP executable.'); } $env = 1 < \func_num_args() ? func_get_arg(1) : null; parent::start($callback, $env); } } isSuccessful()) { throw new InvalidArgumentException('Expected a failed process, but the given process was successful.'); } $error = sprintf('The command "%s" failed.'."\n\nExit Code: %s(%s)\n\nWorking directory: %s", $process->getCommandLine(), $process->getExitCode(), $process->getExitCodeText(), $process->getWorkingDirectory() ); if (!$process->isOutputDisabled()) { $error .= sprintf("\n\nOutput:\n================\n%s\n\nError Output:\n================\n%s", $process->getOutput(), $process->getErrorOutput() ); } parent::__construct($error); $this->process = $process; } public function getProcess() { return $this->process; } } process = $process; $this->timeoutType = $timeoutType; parent::__construct(sprintf( 'The process "%s" exceeded the timeout of %s seconds.', $process->getCommandLine(), $this->getExceededTimeout() )); } public function getProcess() { return $this->process; } public function isGeneralTimeout() { return self::TYPE_GENERAL === $this->timeoutType; } public function isIdleTimeout() { return self::TYPE_IDLE === $this->timeoutType; } public function getExceededTimeout() { switch ($this->timeoutType) { case self::TYPE_GENERAL: return $this->process->getTimeout(); case self::TYPE_IDLE: return $this->process->getIdleTimeout(); default: throw new \LogicException(sprintf('Unknown timeout type "%d".', $this->timeoutType)); } } } onEmpty = $onEmpty; } public function write($input) { if (null === $input) { return; } if ($this->isClosed()) { throw new RuntimeException(sprintf('"%s" is closed.', static::class)); } $this->input[] = ProcessUtils::validateInput(__METHOD__, $input); } public function close() { $this->open = false; } public function isClosed() { return !$this->open; } public function getIterator() { $this->open = true; while ($this->open || $this->input) { if (!$this->input) { yield ''; continue; } $current = array_shift($this->input); if ($current instanceof \Iterator) { foreach ($current as $cur) { yield $cur; } } else { yield $current; } if (!$this->input && $this->open && null !== $onEmpty = $this->onEmpty) { $this->write($onEmpty($this)); } } } } executableFinder = new ExecutableFinder(); } public function find($includeArgs = true) { $args = $this->findArguments(); $args = $includeArgs && $args ? ' '.implode(' ', $args) : ''; if (\defined('HHVM_VERSION')) { return (getenv('PHP_BINARY') ?: PHP_BINARY).$args; } if (PHP_BINARY && \in_array(\PHP_SAPI, ['cli', 'cli-server', 'phpdbg'], true)) { return PHP_BINARY.$args; } if ($php = getenv('PHP_PATH')) { if (!@is_executable($php)) { return false; } return $php; } if ($php = getenv('PHP_PEAR_PHP_BIN')) { if (@is_executable($php)) { return $php; } } if (@is_executable($php = PHP_BINDIR.('\\' === \DIRECTORY_SEPARATOR ? '\\php.exe' : '/php'))) { return $php; } $dirs = [PHP_BINDIR]; if ('\\' === \DIRECTORY_SEPARATOR) { $dirs[] = 'C:\xampp\php\\'; } return $this->executableFinder->find('php', false, $dirs); } public function findArguments() { $arguments = []; if (\defined('HHVM_VERSION')) { $arguments[] = '--php'; } elseif ('phpdbg' === \PHP_SAPI) { $arguments[] = '-qrr'; } return $arguments; } } true]; private $exitcode; private $fallbackStatus = []; private $processInformation; private $outputDisabled = false; private $stdout; private $stderr; private $enhanceWindowsCompatibility = true; private $enhanceSigchildCompatibility; private $process; private $status = self::STATUS_READY; private $incrementalOutputOffset = 0; private $incrementalErrorOutputOffset = 0; private $tty = false; private $pty; private $inheritEnv = false; private $useFileHandles = false; private $processPipes; private $latestSignal; private static $sigchild; public static $exitCodes = [ 0 => 'OK', 1 => 'General error', 2 => 'Misuse of shell builtins', 126 => 'Invoked command cannot execute', 127 => 'Command not found', 128 => 'Invalid exit argument', 129 => 'Hangup', 130 => 'Interrupt', 131 => 'Quit and dump core', 132 => 'Illegal instruction', 133 => 'Trace/breakpoint trap', 134 => 'Process aborted', 135 => 'Bus error: "access to undefined portion of memory object"', 136 => 'Floating point exception: "erroneous arithmetic operation"', 137 => 'Kill (terminate immediately)', 138 => 'User-defined 1', 139 => 'Segmentation violation', 140 => 'User-defined 2', 141 => 'Write to pipe with no one reading', 142 => 'Signal raised by alarm', 143 => 'Termination (request to terminate)', 145 => 'Child process terminated, stopped (or continued*)', 146 => 'Continue if stopped', 147 => 'Stop executing temporarily', 148 => 'Terminal stop signal', 149 => 'Background process attempting to read from tty ("in")', 150 => 'Background process attempting to write to tty ("out")', 151 => 'Urgent data available on socket', 152 => 'CPU time limit exceeded', 153 => 'File size limit exceeded', 154 => 'Signal raised by timer counting virtual time: "virtual timer expired"', 155 => 'Profiling timer expired', 157 => 'Pollable event', 159 => 'Bad syscall', ]; public function __construct($commandline, $cwd = null, array $env = null, $input = null, $timeout = 60, array $options = null) { if (!\function_exists('proc_open')) { throw new RuntimeException('The Process class relies on proc_open, which is not available on your PHP installation.'); } $this->commandline = $commandline; $this->cwd = $cwd; if (null === $this->cwd && (\defined('ZEND_THREAD_SAFE') || '\\' === \DIRECTORY_SEPARATOR)) { $this->cwd = getcwd(); } if (null !== $env) { $this->setEnv($env); } $this->setInput($input); $this->setTimeout($timeout); $this->useFileHandles = '\\' === \DIRECTORY_SEPARATOR; $this->pty = false; $this->enhanceSigchildCompatibility = '\\' !== \DIRECTORY_SEPARATOR && $this->isSigchildEnabled(); if (null !== $options) { @trigger_error(sprintf('The $options parameter of the %s constructor is deprecated since Symfony 3.3 and will be removed in 4.0.', __CLASS__), E_USER_DEPRECATED); $this->options = array_replace($this->options, $options); } } public function __destruct() { $this->stop(0); } public function __clone() { $this->resetProcessData(); } public function run($callback = null) { $env = 1 < \func_num_args() ? func_get_arg(1) : null; $this->start($callback, $env); return $this->wait(); } public function mustRun(callable $callback = null) { if (!$this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) { throw new RuntimeException('This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method.'); } $env = 1 < \func_num_args() ? func_get_arg(1) : null; if (0 !== $this->run($callback, $env)) { throw new ProcessFailedException($this); } return $this; } public function start(callable $callback = null) { if ($this->isRunning()) { throw new RuntimeException('Process is already running.'); } if (2 <= \func_num_args()) { $env = func_get_arg(1); } else { if (__CLASS__ !== static::class) { $r = new \ReflectionMethod($this, __FUNCTION__); if (__CLASS__ !== $r->getDeclaringClass()->getName() && (2 > $r->getNumberOfParameters() || 'env' !== $r->getParameters()[1]->name)) { @trigger_error(sprintf('The %s::start() method expects a second "$env" argument since Symfony 3.3. It will be made mandatory in 4.0.', static::class), E_USER_DEPRECATED); } } $env = null; } $this->resetProcessData(); $this->starttime = $this->lastOutputTime = microtime(true); $this->callback = $this->buildCallback($callback); $this->hasCallback = null !== $callback; $descriptors = $this->getDescriptors(); $inheritEnv = $this->inheritEnv; if (\is_array($commandline = $this->commandline)) { $commandline = implode(' ', array_map([$this, 'escapeArgument'], $commandline)); if ('\\' !== \DIRECTORY_SEPARATOR) { $commandline = 'exec '.$commandline; } } if (null === $env) { $env = $this->env; } else { if ($this->env) { $env += $this->env; } $inheritEnv = true; } if (null !== $env && $inheritEnv) { $env += $this->getDefaultEnv(); } elseif (null !== $env) { @trigger_error('Not inheriting environment variables is deprecated since Symfony 3.3 and will always happen in 4.0. Set "Process::inheritEnvironmentVariables()" to true instead.', E_USER_DEPRECATED); } else { $env = $this->getDefaultEnv(); } if ('\\' === \DIRECTORY_SEPARATOR && $this->enhanceWindowsCompatibility) { $this->options['bypass_shell'] = true; $commandline = $this->prepareWindowsCommandLine($commandline, $env); } elseif (!$this->useFileHandles && $this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) { $descriptors[3] = ['pipe', 'w']; $commandline = '{ ('.$commandline.') <&3 3<&- 3>/dev/null & } 3<&0;'; $commandline .= 'pid=$!; echo $pid >&3; wait $pid; code=$?; echo $code >&3; exit $code'; $ptsWorkaround = fopen(__FILE__, 'r'); } if (\defined('HHVM_VERSION')) { $envPairs = $env; } else { $envPairs = []; foreach ($env as $k => $v) { if (false !== $v) { $envPairs[] = $k.'='.$v; } } } if (!is_dir($this->cwd)) { @trigger_error('The provided cwd does not exist. Command is currently ran against getcwd(). This behavior is deprecated since Symfony 3.4 and will be removed in 4.0.', E_USER_DEPRECATED); } $this->process = @proc_open($commandline, $descriptors, $this->processPipes->pipes, $this->cwd, $envPairs, $this->options); if (!\is_resource($this->process)) { throw new RuntimeException('Unable to launch a new process.'); } $this->status = self::STATUS_STARTED; if (isset($descriptors[3])) { $this->fallbackStatus['pid'] = (int) fgets($this->processPipes->pipes[3]); } if ($this->tty) { return; } $this->updateStatus(false); $this->checkTimeout(); } public function restart(callable $callback = null) { if ($this->isRunning()) { throw new RuntimeException('Process is already running.'); } $env = 1 < \func_num_args() ? func_get_arg(1) : null; $process = clone $this; $process->start($callback, $env); return $process; } public function wait(callable $callback = null) { $this->requireProcessIsStarted(__FUNCTION__); $this->updateStatus(false); if (null !== $callback) { if (!$this->processPipes->haveReadSupport()) { $this->stop(0); throw new \LogicException('Pass the callback to the Process::start method or enableOutput to use a callback with Process::wait.'); } $this->callback = $this->buildCallback($callback); } do { $this->checkTimeout(); $running = '\\' === \DIRECTORY_SEPARATOR ? $this->isRunning() : $this->processPipes->areOpen(); $this->readPipes($running, '\\' !== \DIRECTORY_SEPARATOR || !$running); } while ($running); while ($this->isRunning()) { $this->checkTimeout(); usleep(1000); } if ($this->processInformation['signaled'] && $this->processInformation['termsig'] !== $this->latestSignal) { throw new RuntimeException(sprintf('The process has been signaled with signal "%s".', $this->processInformation['termsig'])); } return $this->exitcode; } public function getPid() { return $this->isRunning() ? $this->processInformation['pid'] : null; } public function signal($signal) { $this->doSignal($signal, true); return $this; } public function disableOutput() { if ($this->isRunning()) { throw new RuntimeException('Disabling output while the process is running is not possible.'); } if (null !== $this->idleTimeout) { throw new LogicException('Output can not be disabled while an idle timeout is set.'); } $this->outputDisabled = true; return $this; } public function enableOutput() { if ($this->isRunning()) { throw new RuntimeException('Enabling output while the process is running is not possible.'); } $this->outputDisabled = false; return $this; } public function isOutputDisabled() { return $this->outputDisabled; } public function getOutput() { $this->readPipesForOutput(__FUNCTION__); if (false === $ret = stream_get_contents($this->stdout, -1, 0)) { return ''; } return $ret; } public function getIncrementalOutput() { $this->readPipesForOutput(__FUNCTION__); $latest = stream_get_contents($this->stdout, -1, $this->incrementalOutputOffset); $this->incrementalOutputOffset = ftell($this->stdout); if (false === $latest) { return ''; } return $latest; } public function getIterator($flags = 0) { $this->readPipesForOutput(__FUNCTION__, false); $clearOutput = !(self::ITER_KEEP_OUTPUT & $flags); $blocking = !(self::ITER_NON_BLOCKING & $flags); $yieldOut = !(self::ITER_SKIP_OUT & $flags); $yieldErr = !(self::ITER_SKIP_ERR & $flags); while (null !== $this->callback || ($yieldOut && !feof($this->stdout)) || ($yieldErr && !feof($this->stderr))) { if ($yieldOut) { $out = stream_get_contents($this->stdout, -1, $this->incrementalOutputOffset); if (isset($out[0])) { if ($clearOutput) { $this->clearOutput(); } else { $this->incrementalOutputOffset = ftell($this->stdout); } yield self::OUT => $out; } } if ($yieldErr) { $err = stream_get_contents($this->stderr, -1, $this->incrementalErrorOutputOffset); if (isset($err[0])) { if ($clearOutput) { $this->clearErrorOutput(); } else { $this->incrementalErrorOutputOffset = ftell($this->stderr); } yield self::ERR => $err; } } if (!$blocking && !isset($out[0]) && !isset($err[0])) { yield self::OUT => ''; } $this->checkTimeout(); $this->readPipesForOutput(__FUNCTION__, $blocking); } } public function clearOutput() { ftruncate($this->stdout, 0); fseek($this->stdout, 0); $this->incrementalOutputOffset = 0; return $this; } public function getErrorOutput() { $this->readPipesForOutput(__FUNCTION__); if (false === $ret = stream_get_contents($this->stderr, -1, 0)) { return ''; } return $ret; } public function getIncrementalErrorOutput() { $this->readPipesForOutput(__FUNCTION__); $latest = stream_get_contents($this->stderr, -1, $this->incrementalErrorOutputOffset); $this->incrementalErrorOutputOffset = ftell($this->stderr); if (false === $latest) { return ''; } return $latest; } public function clearErrorOutput() { ftruncate($this->stderr, 0); fseek($this->stderr, 0); $this->incrementalErrorOutputOffset = 0; return $this; } public function getExitCode() { if (!$this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) { throw new RuntimeException('This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method.'); } $this->updateStatus(false); return $this->exitcode; } public function getExitCodeText() { if (null === $exitcode = $this->getExitCode()) { return null; } return isset(self::$exitCodes[$exitcode]) ? self::$exitCodes[$exitcode] : 'Unknown error'; } public function isSuccessful() { return 0 === $this->getExitCode(); } public function hasBeenSignaled() { $this->requireProcessIsTerminated(__FUNCTION__); if (!$this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) { throw new RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved.'); } return $this->processInformation['signaled']; } public function getTermSignal() { $this->requireProcessIsTerminated(__FUNCTION__); if ($this->isSigchildEnabled() && (!$this->enhanceSigchildCompatibility || -1 === $this->processInformation['termsig'])) { throw new RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved.'); } return $this->processInformation['termsig']; } public function hasBeenStopped() { $this->requireProcessIsTerminated(__FUNCTION__); return $this->processInformation['stopped']; } public function getStopSignal() { $this->requireProcessIsTerminated(__FUNCTION__); return $this->processInformation['stopsig']; } public function isRunning() { if (self::STATUS_STARTED !== $this->status) { return false; } $this->updateStatus(false); return $this->processInformation['running']; } public function isStarted() { return self::STATUS_READY != $this->status; } public function isTerminated() { $this->updateStatus(false); return self::STATUS_TERMINATED == $this->status; } public function getStatus() { $this->updateStatus(false); return $this->status; } public function stop($timeout = 10, $signal = null) { $timeoutMicro = microtime(true) + $timeout; if ($this->isRunning()) { $this->doSignal(15, false); do { usleep(1000); } while ($this->isRunning() && microtime(true) < $timeoutMicro); if ($this->isRunning()) { $this->doSignal($signal ?: 9, false); } } if ($this->isRunning()) { if (isset($this->fallbackStatus['pid'])) { unset($this->fallbackStatus['pid']); return $this->stop(0, $signal); } $this->close(); } return $this->exitcode; } public function addOutput($line) { $this->lastOutputTime = microtime(true); fseek($this->stdout, 0, SEEK_END); fwrite($this->stdout, $line); fseek($this->stdout, $this->incrementalOutputOffset); } public function addErrorOutput($line) { $this->lastOutputTime = microtime(true); fseek($this->stderr, 0, SEEK_END); fwrite($this->stderr, $line); fseek($this->stderr, $this->incrementalErrorOutputOffset); } public function getCommandLine() { return \is_array($this->commandline) ? implode(' ', array_map([$this, 'escapeArgument'], $this->commandline)) : $this->commandline; } public function setCommandLine($commandline) { $this->commandline = $commandline; return $this; } public function getTimeout() { return $this->timeout; } public function getIdleTimeout() { return $this->idleTimeout; } public function setTimeout($timeout) { $this->timeout = $this->validateTimeout($timeout); return $this; } public function setIdleTimeout($timeout) { if (null !== $timeout && $this->outputDisabled) { throw new LogicException('Idle timeout can not be set while the output is disabled.'); } $this->idleTimeout = $this->validateTimeout($timeout); return $this; } public function setTty($tty) { if ('\\' === \DIRECTORY_SEPARATOR && $tty) { throw new RuntimeException('TTY mode is not supported on Windows platform.'); } if ($tty) { static $isTtySupported; if (null === $isTtySupported) { $isTtySupported = (bool) @proc_open('echo 1 >/dev/null', [['file', '/dev/tty', 'r'], ['file', '/dev/tty', 'w'], ['file', '/dev/tty', 'w']], $pipes); } if (!$isTtySupported) { throw new RuntimeException('TTY mode requires /dev/tty to be read/writable.'); } } $this->tty = (bool) $tty; return $this; } public function isTty() { return $this->tty; } public function setPty($bool) { $this->pty = (bool) $bool; return $this; } public function isPty() { return $this->pty; } public function getWorkingDirectory() { if (null === $this->cwd) { return getcwd() ?: null; } return $this->cwd; } public function setWorkingDirectory($cwd) { $this->cwd = $cwd; return $this; } public function getEnv() { return $this->env; } public function setEnv(array $env) { $env = array_filter($env, function ($value) { return !\is_array($value); }); $this->env = $env; return $this; } public function getInput() { return $this->input; } public function setInput($input) { if ($this->isRunning()) { throw new LogicException('Input can not be set while the process is running.'); } $this->input = ProcessUtils::validateInput(__METHOD__, $input); return $this; } public function getOptions() { @trigger_error(sprintf('The %s() method is deprecated since Symfony 3.3 and will be removed in 4.0.', __METHOD__), E_USER_DEPRECATED); return $this->options; } public function setOptions(array $options) { @trigger_error(sprintf('The %s() method is deprecated since Symfony 3.3 and will be removed in 4.0.', __METHOD__), E_USER_DEPRECATED); $this->options = $options; return $this; } public function getEnhanceWindowsCompatibility() { @trigger_error(sprintf('The %s() method is deprecated since Symfony 3.3 and will be removed in 4.0. Enhanced Windows compatibility will always be enabled.', __METHOD__), E_USER_DEPRECATED); return $this->enhanceWindowsCompatibility; } public function setEnhanceWindowsCompatibility($enhance) { @trigger_error(sprintf('The %s() method is deprecated since Symfony 3.3 and will be removed in 4.0. Enhanced Windows compatibility will always be enabled.', __METHOD__), E_USER_DEPRECATED); $this->enhanceWindowsCompatibility = (bool) $enhance; return $this; } public function getEnhanceSigchildCompatibility() { @trigger_error(sprintf('The %s() method is deprecated since Symfony 3.3 and will be removed in 4.0. Sigchild compatibility will always be enabled.', __METHOD__), E_USER_DEPRECATED); return $this->enhanceSigchildCompatibility; } public function setEnhanceSigchildCompatibility($enhance) { @trigger_error(sprintf('The %s() method is deprecated since Symfony 3.3 and will be removed in 4.0. Sigchild compatibility will always be enabled.', __METHOD__), E_USER_DEPRECATED); $this->enhanceSigchildCompatibility = (bool) $enhance; return $this; } public function inheritEnvironmentVariables($inheritEnv = true) { if (!$inheritEnv) { @trigger_error('Not inheriting environment variables is deprecated since Symfony 3.3 and will always happen in 4.0. Set "Process::inheritEnvironmentVariables()" to true instead.', E_USER_DEPRECATED); } $this->inheritEnv = (bool) $inheritEnv; return $this; } public function areEnvironmentVariablesInherited() { @trigger_error(sprintf('The %s() method is deprecated since Symfony 3.3 and will be removed in 4.0. Environment variables will always be inherited.', __METHOD__), E_USER_DEPRECATED); return $this->inheritEnv; } public function checkTimeout() { if (self::STATUS_STARTED !== $this->status) { return; } if (null !== $this->timeout && $this->timeout < microtime(true) - $this->starttime) { $this->stop(0); throw new ProcessTimedOutException($this, ProcessTimedOutException::TYPE_GENERAL); } if (null !== $this->idleTimeout && $this->idleTimeout < microtime(true) - $this->lastOutputTime) { $this->stop(0); throw new ProcessTimedOutException($this, ProcessTimedOutException::TYPE_IDLE); } } public static function isPtySupported() { static $result; if (null !== $result) { return $result; } if ('\\' === \DIRECTORY_SEPARATOR) { return $result = false; } return $result = (bool) @proc_open('echo 1 >/dev/null', [['pty'], ['pty'], ['pty']], $pipes); } private function getDescriptors() { if ($this->input instanceof \Iterator) { $this->input->rewind(); } if ('\\' === \DIRECTORY_SEPARATOR) { $this->processPipes = new WindowsPipes($this->input, !$this->outputDisabled || $this->hasCallback); } else { $this->processPipes = new UnixPipes($this->isTty(), $this->isPty(), $this->input, !$this->outputDisabled || $this->hasCallback); } return $this->processPipes->getDescriptors(); } protected function buildCallback(callable $callback = null) { if ($this->outputDisabled) { return function ($type, $data) use ($callback) { if (null !== $callback) { \call_user_func($callback, $type, $data); } }; } $out = self::OUT; return function ($type, $data) use ($callback, $out) { if ($out == $type) { $this->addOutput($data); } else { $this->addErrorOutput($data); } if (null !== $callback) { \call_user_func($callback, $type, $data); } }; } protected function updateStatus($blocking) { if (self::STATUS_STARTED !== $this->status) { return; } $this->processInformation = proc_get_status($this->process); $running = $this->processInformation['running']; $this->readPipes($running && $blocking, '\\' !== \DIRECTORY_SEPARATOR || !$running); if ($this->fallbackStatus && $this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) { $this->processInformation = $this->fallbackStatus + $this->processInformation; } if (!$running) { $this->close(); } } protected function isSigchildEnabled() { if (null !== self::$sigchild) { return self::$sigchild; } if (!\function_exists('phpinfo') || \defined('HHVM_VERSION')) { return self::$sigchild = false; } ob_start(); phpinfo(INFO_GENERAL); return self::$sigchild = false !== strpos(ob_get_clean(), '--enable-sigchild'); } private function readPipesForOutput($caller, $blocking = false) { if ($this->outputDisabled) { throw new LogicException('Output has been disabled.'); } $this->requireProcessIsStarted($caller); $this->updateStatus($blocking); } private function validateTimeout($timeout) { $timeout = (float) $timeout; if (0.0 === $timeout) { $timeout = null; } elseif ($timeout < 0) { throw new InvalidArgumentException('The timeout value must be a valid positive integer or float number.'); } return $timeout; } private function readPipes($blocking, $close) { $result = $this->processPipes->readAndWrite($blocking, $close); $callback = $this->callback; foreach ($result as $type => $data) { if (3 !== $type) { $callback(self::STDOUT === $type ? self::OUT : self::ERR, $data); } elseif (!isset($this->fallbackStatus['signaled'])) { $this->fallbackStatus['exitcode'] = (int) $data; } } } private function close() { $this->processPipes->close(); if (\is_resource($this->process)) { proc_close($this->process); } $this->exitcode = $this->processInformation['exitcode']; $this->status = self::STATUS_TERMINATED; if (-1 === $this->exitcode) { if ($this->processInformation['signaled'] && 0 < $this->processInformation['termsig']) { $this->exitcode = 128 + $this->processInformation['termsig']; } elseif ($this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) { $this->processInformation['signaled'] = true; $this->processInformation['termsig'] = -1; } } $this->callback = null; return $this->exitcode; } private function resetProcessData() { $this->starttime = null; $this->callback = null; $this->exitcode = null; $this->fallbackStatus = []; $this->processInformation = null; $this->stdout = fopen('php://temp/maxmemory:'.(1024 * 1024), 'w+b'); $this->stderr = fopen('php://temp/maxmemory:'.(1024 * 1024), 'w+b'); $this->process = null; $this->latestSignal = null; $this->status = self::STATUS_READY; $this->incrementalOutputOffset = 0; $this->incrementalErrorOutputOffset = 0; } private function doSignal($signal, $throwException) { if (null === $pid = $this->getPid()) { if ($throwException) { throw new LogicException('Can not send signal on a non running process.'); } return false; } if ('\\' === \DIRECTORY_SEPARATOR) { exec(sprintf('taskkill /F /T /PID %d 2>&1', $pid), $output, $exitCode); if ($exitCode && $this->isRunning()) { if ($throwException) { throw new RuntimeException(sprintf('Unable to kill the process (%s).', implode(' ', $output))); } return false; } } else { if (!$this->enhanceSigchildCompatibility || !$this->isSigchildEnabled()) { $ok = @proc_terminate($this->process, $signal); } elseif (\function_exists('posix_kill')) { $ok = @posix_kill($pid, $signal); } elseif ($ok = proc_open(sprintf('kill -%d %d', $signal, $pid), [2 => ['pipe', 'w']], $pipes)) { $ok = false === fgets($pipes[2]); } if (!$ok) { if ($throwException) { throw new RuntimeException(sprintf('Error while sending signal `%s`.', $signal)); } return false; } } $this->latestSignal = (int) $signal; $this->fallbackStatus['signaled'] = true; $this->fallbackStatus['exitcode'] = -1; $this->fallbackStatus['termsig'] = $this->latestSignal; return true; } private function prepareWindowsCommandLine($cmd, array &$env) { $uid = uniqid('', true); $varCount = 0; $varCache = []; $cmd = preg_replace_callback( '/"(?:( [^"%!^]*+ (?: (?: !LF! | "(?:\^[%!^])?+" ) [^"%!^]*+ )++ ) | [^"]*+ )"/x', function ($m) use (&$env, &$varCache, &$varCount, $uid) { if (!isset($m[1])) { return $m[0]; } if (isset($varCache[$m[0]])) { return $varCache[$m[0]]; } if (false !== strpos($value = $m[1], "\0")) { $value = str_replace("\0", '?', $value); } if (false === strpbrk($value, "\"%!\n")) { return '"'.$value.'"'; } $value = str_replace(['!LF!', '"^!"', '"^%"', '"^^"', '""'], ["\n", '!', '%', '^', '"'], $value); $value = '"'.preg_replace('/(\\\\*)"/', '$1$1\\"', $value).'"'; $var = $uid.++$varCount; $env[$var] = $value; return $varCache[$m[0]] = '!'.$var.'!'; }, $cmd ); $cmd = 'cmd /V:ON /E:ON /D /C ('.str_replace("\n", ' ', $cmd).')'; foreach ($this->processPipes->getFiles() as $offset => $filename) { $cmd .= ' '.$offset.'>"'.$filename.'"'; } return $cmd; } private function requireProcessIsStarted($functionName) { if (!$this->isStarted()) { throw new LogicException(sprintf('Process must be started before calling "%s()".', $functionName)); } } private function requireProcessIsTerminated($functionName) { if (!$this->isTerminated()) { throw new LogicException(sprintf('Process must be terminated before calling "%s()".', $functionName)); } } private function escapeArgument($argument) { if ('\\' !== \DIRECTORY_SEPARATOR) { return "'".str_replace("'", "'\\''", $argument)."'"; } if ('' === $argument = (string) $argument) { return '""'; } if (false !== strpos($argument, "\0")) { $argument = str_replace("\0", '?', $argument); } if (!preg_match('/[\/()%!^"<>&|\s]/', $argument)) { return $argument; } $argument = preg_replace('/(\\\\+)$/', '$1$1', $argument); return '"'.str_replace(['"', '^', '%', '!', "\n"], ['""', '"^^"', '"^%"', '"^!"', '!LF!'], $argument).'"'; } private function getDefaultEnv() { $env = []; foreach ($_SERVER as $k => $v) { if (\is_string($v) && false !== $v = getenv($k)) { $env[$k] = $v; } } foreach ($_ENV as $k => $v) { if (\is_string($v)) { $env[$k] = $v; } } return $env; } } arguments = $arguments; } public static function create(array $arguments = []) { return new static($arguments); } public function add($argument) { $this->arguments[] = $argument; return $this; } public function setPrefix($prefix) { $this->prefix = \is_array($prefix) ? $prefix : [$prefix]; return $this; } public function setArguments(array $arguments) { $this->arguments = $arguments; return $this; } public function setWorkingDirectory($cwd) { $this->cwd = $cwd; return $this; } public function inheritEnvironmentVariables($inheritEnv = true) { $this->inheritEnv = $inheritEnv; return $this; } public function setEnv($name, $value) { $this->env[$name] = $value; return $this; } public function addEnvironmentVariables(array $variables) { $this->env = array_replace($this->env, $variables); return $this; } public function setInput($input) { $this->input = ProcessUtils::validateInput(__METHOD__, $input); return $this; } public function setTimeout($timeout) { if (null === $timeout) { $this->timeout = null; return $this; } $timeout = (float) $timeout; if ($timeout < 0) { throw new InvalidArgumentException('The timeout value must be a valid positive integer or float number.'); } $this->timeout = $timeout; return $this; } public function setOption($name, $value) { $this->options[$name] = $value; return $this; } public function disableOutput() { $this->outputDisabled = true; return $this; } public function enableOutput() { $this->outputDisabled = false; return $this; } public function getProcess() { if (0 === \count($this->prefix) && 0 === \count($this->arguments)) { throw new LogicException('You must add() command arguments before calling getProcess().'); } $arguments = array_merge($this->prefix, $this->arguments); $process = new Process($arguments, $this->cwd, $this->env, $this->input, $this->timeout, $this->options); $process->setCommandLine($process->getCommandLine()); if ($this->inheritEnv) { $process->inheritEnvironmentVariables(); } if ($this->outputDisabled) { $process->disableOutput(); } return $process; } } $max) { throw new Error( 'Minimum value must be less than or equal to the maximum value' ); } if ($max === $min) { return (int) $min; } $attempts = $bits = $bytes = $mask = $valueShift = 0; $range = $max - $min; if (!is_int($range)) { /** @CodesInChaos @oittaa @ref @ref @ref */ $bytes = PHP_INT_SIZE; $mask = ~0; } else { while ($range > 0) { if ($bits % 8 === 0) { ++$bytes; } ++$bits; $range >>= 1; $mask = $mask << 1 | 1; } $valueShift = $min; } $val = 0; /** @psalm */ do { if ($attempts > 128) { throw new Exception( 'random_int: RNG is broken - too many rejections' ); } $randomByteString = random_bytes($bytes); $val &= 0; for ($i = 0; $i < $bytes; ++$i) { $val |= ord($randomByteString[$i]) << ($i * 8); } $val &= $mask; $val += $valueShift; ++$attempts; } while (!is_int($val) || $val > $max || $val < $min); return (int) $val; } } 2147483647) { $buf = ''; for ($i = 0; $i < $bytes; $i += 1073741824) { $n = ($bytes - $i) > 1073741824 ? 1073741824 : $bytes - $i; $buf .= \Sodium\randombytes_buf($n); } } else { $buf = \Sodium\randombytes_buf($bytes); } if (is_string($buf)) { if (RandomCompat_strlen($buf) === $bytes) { return $buf; } } throw new Exception( 'Could not gather sufficient random data' ); } } = 70000) { return; } if (!defined('RANDOM_COMPAT_READ_BUFFER')) { define('RANDOM_COMPAT_READ_BUFFER', 8); } $RandomCompatDIR = dirname(__FILE__); require_once $RandomCompatDIR . DIRECTORY_SEPARATOR . 'byte_safe_strings.php'; require_once $RandomCompatDIR . DIRECTORY_SEPARATOR . 'cast_to_int.php'; require_once $RandomCompatDIR . DIRECTORY_SEPARATOR . 'error_polyfill.php'; if (!is_callable('random_bytes')) { if (extension_loaded('libsodium')) { if (PHP_VERSION_ID >= 50300 && is_callable('\\Sodium\\randombytes_buf')) { require_once $RandomCompatDIR . DIRECTORY_SEPARATOR . 'random_bytes_libsodium.php'; } elseif (method_exists('Sodium', 'randombytes_buf')) { require_once $RandomCompatDIR . DIRECTORY_SEPARATOR . 'random_bytes_libsodium_legacy.php'; } } if (DIRECTORY_SEPARATOR === '/') { $RandomCompatUrandom = true; $RandomCompat_basedir = ini_get('open_basedir'); if (!empty($RandomCompat_basedir)) { $RandomCompat_open_basedir = explode( PATH_SEPARATOR, strtolower($RandomCompat_basedir) ); $RandomCompatUrandom = (array() !== array_intersect( array('/dev', '/dev/', '/dev/urandom'), $RandomCompat_open_basedir )); $RandomCompat_open_basedir = null; } if ( !is_callable('random_bytes') && $RandomCompatUrandom && @is_readable('/dev/urandom') ) { require_once $RandomCompatDIR . DIRECTORY_SEPARATOR . 'random_bytes_dev_urandom.php'; } $RandomCompat_basedir = null; } else { $RandomCompatUrandom = false; } if ( !is_callable('random_bytes') && (DIRECTORY_SEPARATOR === '/' || PHP_VERSION_ID >= 50307) && ( DIRECTORY_SEPARATOR !== '/' || (PHP_VERSION_ID <= 50609 || PHP_VERSION_ID >= 50613) ) && extension_loaded('mcrypt') ) { require_once $RandomCompatDIR . DIRECTORY_SEPARATOR . 'random_bytes_mcrypt.php'; } $RandomCompatUrandom = null; if ( !is_callable('random_bytes') && extension_loaded('com_dotnet') && class_exists('COM') ) { $RandomCompat_disabled_classes = preg_split( '#\s*,\s*#', strtolower(ini_get('disable_classes')) ); if (!in_array('com', $RandomCompat_disabled_classes)) { try { $RandomCompatCOMtest = new COM('CAPICOM.Utilities.1'); if (method_exists($RandomCompatCOMtest, 'GetRandom')) { require_once $RandomCompatDIR . DIRECTORY_SEPARATOR . 'random_bytes_com_dotnet.php'; } } catch (com_exception $e) { } } $RandomCompat_disabled_classes = null; $RandomCompatCOMtest = null; } if (!is_callable('random_bytes')) { /** @psalm */ function random_bytes($length) { unset($length); throw new Exception( 'There is no suitable CSPRNG installed on your system' ); return ''; } } } if (!is_callable('random_int')) { require_once $RandomCompatDIR . DIRECTORY_SEPARATOR . 'random_int.php'; } $RandomCompatDIR = null; GetRandom($bytes, 0)); if (RandomCompat_strlen($buf) >= $bytes) { return (string) RandomCompat_substr($buf, 0, $bytes); } ++$execCount; } while ($execCount < $bytes); throw new Exception( 'Could not gather sufficient random data' ); } } 0); if (is_string($buf)) { if (RandomCompat_strlen($buf) === $bytes) { return $buf; } } } throw new Exception( 'Error reading from source device' ); } } RandomCompat_strlen($binary_string)) { return ''; } return (string) mb_substr( (string) $binary_string, (int) $start, (int) $length, '8bit' ); } } else { function RandomCompat_substr($binary_string, $start, $length = null) { if (!is_string($binary_string)) { throw new TypeError( 'RandomCompat_substr(): First argument should be a string' ); } if (!is_int($start)) { throw new TypeError( 'RandomCompat_substr(): Second argument should be an integer' ); } if ($length !== null) { if (!is_int($length)) { throw new TypeError( 'RandomCompat_substr(): Third argument should be an integer, or omitted' ); } return (string) substr( (string )$binary_string, (int) $start, (int) $length ); } return (string) substr( (string) $binary_string, (int) $start ); } } } 2147483647) { for ($i = 0; $i < $bytes; $i += 1073741824) { $n = ($bytes - $i) > 1073741824 ? 1073741824 : $bytes - $i; $buf .= Sodium::randombytes_buf((int) $n); } } else { $buf .= Sodium::randombytes_buf((int) $bytes); } if (is_string($buf)) { if (RandomCompat_strlen($buf) === $bytes) { return $buf; } } throw new Exception( 'Could not gather sufficient random data' ); } } ~PHP_INT_MAX && $number < PHP_INT_MAX ) { $number = (int) $number; } if (is_int($number)) { return (int) $number; } elseif (!$fail_open) { throw new TypeError( 'Expected an integer.' ); } return $number; } } $vendorDir . '/symfony/polyfill-php70/Resources/stubs/ArithmeticError.php', 'AssertionError' => $vendorDir . '/symfony/polyfill-php70/Resources/stubs/AssertionError.php', 'DivisionByZeroError' => $vendorDir . '/symfony/polyfill-php70/Resources/stubs/DivisionByZeroError.php', 'Error' => $vendorDir . '/symfony/polyfill-php70/Resources/stubs/Error.php', 'ParseError' => $vendorDir . '/symfony/polyfill-php70/Resources/stubs/ParseError.php', 'PhpCsFixer\\Diff\\GeckoPackages\\DiffOutputBuilder\\ConfigurationException' => $vendorDir . '/php-cs-fixer/diff/src/GeckoPackages/DiffOutputBuilder/ConfigurationException.php', 'PhpCsFixer\\Diff\\GeckoPackages\\DiffOutputBuilder\\UnifiedDiffOutputBuilder' => $vendorDir . '/php-cs-fixer/diff/src/GeckoPackages/DiffOutputBuilder/UnifiedDiffOutputBuilder.php', 'PhpCsFixer\\Diff\\v1_4\\Chunk' => $vendorDir . '/php-cs-fixer/diff/src/v1_4/Chunk.php', 'PhpCsFixer\\Diff\\v1_4\\Diff' => $vendorDir . '/php-cs-fixer/diff/src/v1_4/Diff.php', 'PhpCsFixer\\Diff\\v1_4\\Differ' => $vendorDir . '/php-cs-fixer/diff/src/v1_4/Differ.php', 'PhpCsFixer\\Diff\\v1_4\\LCS\\LongestCommonSubsequence' => $vendorDir . '/php-cs-fixer/diff/src/v1_4/LCS/LongestCommonSubsequence.php', 'PhpCsFixer\\Diff\\v1_4\\LCS\\MemoryEfficientImplementation' => $vendorDir . '/php-cs-fixer/diff/src/v1_4/LCS/MemoryEfficientLongestCommonSubsequenceImplementation.php', 'PhpCsFixer\\Diff\\v1_4\\LCS\\TimeEfficientImplementation' => $vendorDir . '/php-cs-fixer/diff/src/v1_4/LCS/TimeEfficientLongestCommonSubsequenceImplementation.php', 'PhpCsFixer\\Diff\\v1_4\\Line' => $vendorDir . '/php-cs-fixer/diff/src/v1_4/Line.php', 'PhpCsFixer\\Diff\\v1_4\\Parser' => $vendorDir . '/php-cs-fixer/diff/src/v1_4/Parser.php', 'PhpCsFixer\\Diff\\v2_0\\Chunk' => $vendorDir . '/php-cs-fixer/diff/src/v2_0/Chunk.php', 'PhpCsFixer\\Diff\\v2_0\\Diff' => $vendorDir . '/php-cs-fixer/diff/src/v2_0/Diff.php', 'PhpCsFixer\\Diff\\v2_0\\Differ' => $vendorDir . '/php-cs-fixer/diff/src/v2_0/Differ.php', 'PhpCsFixer\\Diff\\v2_0\\Exception' => $vendorDir . '/php-cs-fixer/diff/src/v2_0/Exception/Exception.php', 'PhpCsFixer\\Diff\\v2_0\\InvalidArgumentException' => $vendorDir . '/php-cs-fixer/diff/src/v2_0/Exception/InvalidArgumentException.php', 'PhpCsFixer\\Diff\\v2_0\\Line' => $vendorDir . '/php-cs-fixer/diff/src/v2_0/Line.php', 'PhpCsFixer\\Diff\\v2_0\\LongestCommonSubsequenceCalculator' => $vendorDir . '/php-cs-fixer/diff/src/v2_0/LongestCommonSubsequenceCalculator.php', 'PhpCsFixer\\Diff\\v2_0\\MemoryEfficientLongestCommonSubsequenceCalculator' => $vendorDir . '/php-cs-fixer/diff/src/v2_0/MemoryEfficientLongestCommonSubsequenceCalculator.php', 'PhpCsFixer\\Diff\\v2_0\\Output\\AbstractChunkOutputBuilder' => $vendorDir . '/php-cs-fixer/diff/src/v2_0/Output/AbstractChunkOutputBuilder.php', 'PhpCsFixer\\Diff\\v2_0\\Output\\DiffOnlyOutputBuilder' => $vendorDir . '/php-cs-fixer/diff/src/v2_0/Output/DiffOnlyOutputBuilder.php', 'PhpCsFixer\\Diff\\v2_0\\Output\\DiffOutputBuilderInterface' => $vendorDir . '/php-cs-fixer/diff/src/v2_0/Output/DiffOutputBuilderInterface.php', 'PhpCsFixer\\Diff\\v2_0\\Output\\UnifiedDiffOutputBuilder' => $vendorDir . '/php-cs-fixer/diff/src/v2_0/Output/UnifiedDiffOutputBuilder.php', 'PhpCsFixer\\Diff\\v2_0\\Parser' => $vendorDir . '/php-cs-fixer/diff/src/v2_0/Parser.php', 'PhpCsFixer\\Diff\\v2_0\\TimeEfficientLongestCommonSubsequenceCalculator' => $vendorDir . '/php-cs-fixer/diff/src/v2_0/TimeEfficientLongestCommonSubsequenceCalculator.php', 'PhpCsFixer\\Diff\\v3_0\\Chunk' => $vendorDir . '/php-cs-fixer/diff/src/v3_0/Chunk.php', 'PhpCsFixer\\Diff\\v3_0\\ConfigurationException' => $vendorDir . '/php-cs-fixer/diff/src/v3_0/Exception/ConfigurationException.php', 'PhpCsFixer\\Diff\\v3_0\\Diff' => $vendorDir . '/php-cs-fixer/diff/src/v3_0/Diff.php', 'PhpCsFixer\\Diff\\v3_0\\Differ' => $vendorDir . '/php-cs-fixer/diff/src/v3_0/Differ.php', 'PhpCsFixer\\Diff\\v3_0\\Exception' => $vendorDir . '/php-cs-fixer/diff/src/v3_0/Exception/Exception.php', 'PhpCsFixer\\Diff\\v3_0\\InvalidArgumentException' => $vendorDir . '/php-cs-fixer/diff/src/v3_0/Exception/InvalidArgumentException.php', 'PhpCsFixer\\Diff\\v3_0\\Line' => $vendorDir . '/php-cs-fixer/diff/src/v3_0/Line.php', 'PhpCsFixer\\Diff\\v3_0\\LongestCommonSubsequenceCalculator' => $vendorDir . '/php-cs-fixer/diff/src/v3_0/LongestCommonSubsequenceCalculator.php', 'PhpCsFixer\\Diff\\v3_0\\MemoryEfficientLongestCommonSubsequenceCalculator' => $vendorDir . '/php-cs-fixer/diff/src/v3_0/MemoryEfficientLongestCommonSubsequenceCalculator.php', 'PhpCsFixer\\Diff\\v3_0\\Output\\AbstractChunkOutputBuilder' => $vendorDir . '/php-cs-fixer/diff/src/v3_0/Output/AbstractChunkOutputBuilder.php', 'PhpCsFixer\\Diff\\v3_0\\Output\\DiffOnlyOutputBuilder' => $vendorDir . '/php-cs-fixer/diff/src/v3_0/Output/DiffOnlyOutputBuilder.php', 'PhpCsFixer\\Diff\\v3_0\\Output\\DiffOutputBuilderInterface' => $vendorDir . '/php-cs-fixer/diff/src/v3_0/Output/DiffOutputBuilderInterface.php', 'PhpCsFixer\\Diff\\v3_0\\Output\\StrictUnifiedDiffOutputBuilder' => $vendorDir . '/php-cs-fixer/diff/src/v3_0/Output/StrictUnifiedDiffOutputBuilder.php', 'PhpCsFixer\\Diff\\v3_0\\Output\\UnifiedDiffOutputBuilder' => $vendorDir . '/php-cs-fixer/diff/src/v3_0/Output/UnifiedDiffOutputBuilder.php', 'PhpCsFixer\\Diff\\v3_0\\Parser' => $vendorDir . '/php-cs-fixer/diff/src/v3_0/Parser.php', 'PhpCsFixer\\Diff\\v3_0\\TimeEfficientLongestCommonSubsequenceCalculator' => $vendorDir . '/php-cs-fixer/diff/src/v3_0/TimeEfficientLongestCommonSubsequenceCalculator.php', 'PhpCsFixer\\Tests\\TestCase' => $baseDir . '/tests/TestCase.php', 'PhpCsFixer\\Tests\\Test\\AbstractFixerTestCase' => $baseDir . '/tests/Test/AbstractFixerTestCase.php', 'PhpCsFixer\\Tests\\Test\\AbstractIntegrationCaseFactory' => $baseDir . '/tests/Test/AbstractIntegrationCaseFactory.php', 'PhpCsFixer\\Tests\\Test\\AbstractIntegrationTestCase' => $baseDir . '/tests/Test/AbstractIntegrationTestCase.php', 'PhpCsFixer\\Tests\\Test\\Assert\\AssertTokensTrait' => $baseDir . '/tests/Test/Assert/AssertTokensTrait.php', 'PhpCsFixer\\Tests\\Test\\IntegrationCase' => $baseDir . '/tests/Test/IntegrationCase.php', 'PhpCsFixer\\Tests\\Test\\IntegrationCaseFactory' => $baseDir . '/tests/Test/IntegrationCaseFactory.php', 'PhpCsFixer\\Tests\\Test\\IntegrationCaseFactoryInterface' => $baseDir . '/tests/Test/IntegrationCaseFactoryInterface.php', 'PhpCsFixer\\Tests\\Test\\InternalIntegrationCaseFactory' => $baseDir . '/tests/Test/InternalIntegrationCaseFactory.php', 'PhpCsFixer\\Tests\\Test\\IsIdenticalConstraint' => $baseDir . '/tests/Test/IsIdenticalConstraint.php', 'SessionUpdateTimestampHandlerInterface' => $vendorDir . '/symfony/polyfill-php70/Resources/stubs/SessionUpdateTimestampHandlerInterface.php', 'TypeError' => $vendorDir . '/symfony/polyfill-php70/Resources/stubs/TypeError.php', ); time = $start ? round((microtime(true) - $start) * 1000) : 0; $this->envAllowXdebug = $envAllowXdebug; $this->debug = $debug && defined('STDERR'); } public function setLogger(LoggerInterface $logger) { $this->logger = $logger; } public function report($op, $data) { if ($this->logger || $this->debug) { call_user_func(array($this, 'report'.$op), $data); } } private function output($text, $level = null) { if ($this->logger) { $this->logger->log($level ?: LogLevel::DEBUG, $text); } if ($this->debug) { fwrite(STDERR, sprintf('xdebug-handler[%d] %s', getmypid(), $text.PHP_EOL)); } } private function reportCheck($loaded) { $this->loaded = $loaded; $this->output('Checking '.$this->envAllowXdebug); } private function reportError($error) { $this->output(sprintf('No restart (%s)', $error), LogLevel::WARNING); } private function reportInfo($info) { $this->output($info); } private function reportNoRestart() { $this->output($this->getLoadedMessage()); if ($this->loaded) { $text = sprintf('No restart (%s)', $this->getEnvAllow()); if (!getenv($this->envAllowXdebug)) { $text .= ' Allowed by application'; } $this->output($text); } } private function reportRestart() { $this->output($this->getLoadedMessage()); Process::setEnv(self::ENV_RESTART, (string) microtime(true)); } private function reportRestarted() { $loaded = $this->getLoadedMessage(); $text = sprintf('Restarted (%d ms). %s', $this->time, $loaded); $level = $this->loaded ? LogLevel::WARNING : null; $this->output($text, $level); } private function reportRestarting($command) { $text = sprintf('Process restarting (%s)', $this->getEnvAllow()); $this->output($text); $text = 'Running '.$command; $this->output($text); } private function getEnvAllow() { return $this->envAllowXdebug.'='.getenv($this->envAllowXdebug); } private function getLoadedMessage() { $loaded = $this->loaded ? sprintf('loaded (%s)', $this->loaded) : 'not loaded'; return 'The Xdebug extension is '.$loaded; } } getDataAndReset(); return array(); } public function useStandard() { if ($data = $this->getDataAndReset()) { return array('-n', '-c', $data['tmpIni']); } return array(); } public function usePersistent() { if ($data = $this->getDataAndReset()) { Process::setEnv('PHPRC', $data['tmpIni']); Process::setEnv('PHP_INI_SCAN_DIR', ''); } return array(); } private function getDataAndReset() { if ($data = XdebugHandler::getRestartSettings()) { Process::setEnv('PHPRC', $data['phprc']); Process::setEnv('PHP_INI_SCAN_DIR', $data['scanDir']); } return $data; } } envAllowXdebug = self::$name.self::SUFFIX_ALLOW; $this->envOriginalInis = self::$name.self::SUFFIX_INIS; $this->colorOption = $colorOption; if (extension_loaded('xdebug')) { $ext = new \ReflectionExtension('xdebug'); $this->loaded = $ext->getVersion() ?: 'unknown'; } if ($this->cli = PHP_SAPI === 'cli') { $this->debug = getenv(self::DEBUG); } $this->statusWriter = new Status($this->envAllowXdebug, (bool) $this->debug); } public function setLogger(LoggerInterface $logger) { $this->statusWriter->setLogger($logger); return $this; } public function setMainScript($script) { $this->script = $script; return $this; } public function setPersistent() { $this->persistent = true; return $this; } public function check() { $this->notify(Status::CHECK, $this->loaded); $envArgs = explode('|', (string) getenv($this->envAllowXdebug)); if (empty($envArgs[0]) && $this->requiresRestart((bool) $this->loaded)) { $this->notify(Status::RESTART); if ($this->prepareRestart()) { $command = $this->getCommand(); $this->notify(Status::RESTARTING, $command); $this->restart($command); } return; } if (self::RESTART_ID === $envArgs[0] && count($envArgs) === 5) { $this->notify(Status::RESTARTED); Process::setEnv($this->envAllowXdebug); self::$inRestart = true; if (!$this->loaded) { self::$skipped = $envArgs[1]; } $this->setEnvRestartSettings($envArgs); return; } $this->notify(Status::NORESTART); if ($settings = self::getRestartSettings()) { $this->syncSettings($settings); } } public static function getAllIniFiles() { if (!empty(self::$name)) { $env = getenv(self::$name.self::SUFFIX_INIS); if (false !== $env) { return explode(PATH_SEPARATOR, $env); } } $paths = array((string) php_ini_loaded_file()); if ($scanned = php_ini_scanned_files()) { $paths = array_merge($paths, array_map('trim', explode(',', $scanned))); } return $paths; } public static function getRestartSettings() { $envArgs = explode('|', (string) getenv(self::RESTART_SETTINGS)); if (count($envArgs) !== 6 || (!self::$inRestart && php_ini_loaded_file() !== $envArgs[0])) { return; } return array( 'tmpIni' => $envArgs[0], 'scannedInis' => (bool) $envArgs[1], 'scanDir' => '*' === $envArgs[2] ? false : $envArgs[2], 'phprc' => '*' === $envArgs[3] ? false : $envArgs[3], 'inis' => explode(PATH_SEPARATOR, $envArgs[4]), 'skipped' => $envArgs[5], ); } public static function getSkippedVersion() { return (string) self::$skipped; } protected function requiresRestart($isLoaded) { return $isLoaded; } protected function restart($command) { $this->doRestart($command); } private function doRestart($command) { passthru($command, $exitCode); $this->notify(Status::INFO, 'Restarted process exited '.$exitCode); if ($this->debug === '2') { $this->notify(Status::INFO, 'Temp ini saved: '.$this->tmpIni); } else { @unlink($this->tmpIni); } exit($exitCode); } private function prepareRestart() { $error = ''; $iniFiles = self::getAllIniFiles(); $scannedInis = count($iniFiles) > 1; $tmpDir = sys_get_temp_dir(); if (!$this->cli) { $error = 'Unsupported SAPI: '.PHP_SAPI; } elseif (!defined('PHP_BINARY')) { $error = 'PHP version is too old: '.PHP_VERSION; } elseif (!$this->checkConfiguration($info)) { $error = $info; } elseif (!$this->checkScanDirConfig()) { $error = 'PHP version does not report scanned inis: '.PHP_VERSION; } elseif (!$this->checkMainScript()) { $error = 'Unable to access main script: '.$this->script; } elseif (!$this->writeTmpIni($iniFiles, $tmpDir, $error)) { $error = $error ?: 'Unable to create temp ini file at: '.$tmpDir; } elseif (!$this->setEnvironment($scannedInis, $iniFiles)) { $error = 'Unable to set environment variables'; } if ($error) { $this->notify(Status::ERROR, $error); } return empty($error); } private function writeTmpIni(array $iniFiles, $tmpDir, &$error) { if (!$this->tmpIni = @tempnam($tmpDir, '')) { return false; } if (empty($iniFiles[0])) { array_shift($iniFiles); } $content = ''; $regex = '/^\s*(zend_extension\s*=.*xdebug.*)$/mi'; foreach ($iniFiles as $file) { if (($data = @file_get_contents($file)) === false) { $error = 'Unable to read ini: '.$file; return false; } $content .= preg_replace($regex, ';$1', $data).PHP_EOL; } if ($config = parse_ini_string($content)) { $loaded = ini_get_all(null, false); $content .= $this->mergeLoadedConfig($loaded, $config); } $content .= 'opcache.enable_cli=0'.PHP_EOL; return @file_put_contents($this->tmpIni, $content); } private function getCommand() { $php = array(PHP_BINARY); $args = array_slice($_SERVER['argv'], 1); if (!$this->persistent) { array_push($php, '-n', '-c', $this->tmpIni); } if (defined('STDOUT') && Process::supportsColor(STDOUT)) { $args = Process::addColorOption($args, $this->colorOption); } $args = array_merge($php, array($this->script), $args); $cmd = Process::escape(array_shift($args), true, true); foreach ($args as $arg) { $cmd .= ' '.Process::escape($arg); } return $cmd; } private function setEnvironment($scannedInis, array $iniFiles) { $scanDir = getenv('PHP_INI_SCAN_DIR'); $phprc = getenv('PHPRC'); if (!putenv($this->envOriginalInis.'='.implode(PATH_SEPARATOR, $iniFiles))) { return false; } if ($this->persistent) { if (!putenv('PHP_INI_SCAN_DIR=') || !putenv('PHPRC='.$this->tmpIni)) { return false; } } $envArgs = array( self::RESTART_ID, $this->loaded, (int) $scannedInis, false === $scanDir ? '*' : $scanDir, false === $phprc ? '*' : $phprc, ); return putenv($this->envAllowXdebug.'='.implode('|', $envArgs)); } private function notify($op, $data = null) { $this->statusWriter->report($op, $data); } private function mergeLoadedConfig(array $loadedConfig, array $iniConfig) { $content = ''; foreach ($loadedConfig as $name => $value) { if (!is_string($value) || strpos($name, 'xdebug') === 0 || $name === 'apc.mmap_file_mask') { continue; } if (!isset($iniConfig[$name]) || $iniConfig[$name] !== $value) { $content .= $name.'="'.addcslashes($value, '\\"').'"'.PHP_EOL; } } return $content; } private function checkMainScript() { if (null !== $this->script) { return file_exists($this->script) || '--' === $this->script; } if (file_exists($this->script = $_SERVER['argv'][0])) { return true; } $options = PHP_VERSION_ID >= 50306 ? DEBUG_BACKTRACE_IGNORE_ARGS : false; $trace = debug_backtrace($options); if (($main = end($trace)) && isset($main['file'])) { return file_exists($this->script = $main['file']); } return false; } private function setEnvRestartSettings($envArgs) { $settings = array( php_ini_loaded_file(), $envArgs[2], $envArgs[3], $envArgs[4], getenv($this->envOriginalInis), self::$skipped, ); Process::setEnv(self::RESTART_SETTINGS, implode('|', $settings)); } private function syncSettings(array $settings) { if (false === getenv($this->envOriginalInis)) { Process::setEnv($this->envOriginalInis, implode(PATH_SEPARATOR, $settings['inis'])); } self::$skipped = $settings['skipped']; $this->notify(Status::INFO, 'Process called with existing restart settings'); } private function checkScanDirConfig() { return !(getenv('PHP_INI_SCAN_DIR') && !PHP_CONFIG_FILE_SCAN_DIR && (PHP_VERSION_ID < 70113 || PHP_VERSION_ID === 70200)); } private function checkConfiguration(&$info) { if (false !== strpos(ini_get('disable_functions'), 'passthru')) { $info = 'passthru function is disabled'; return false; } if (extension_loaded('uopz') && !ini_get('uopz.disable')) { if (function_exists('uopz_allow_exit')) { @uopz_allow_exit(true); } else { $info = 'uopz extension is not compatible'; return false; } } return true; } } ()') !== false; } elseif ($module && !$dquotes && $quote) { $meta = false; } } if ($quote) { $arg = '"'.preg_replace('/(\\\\*)$/', '$1$1', $arg).'"'; } if ($meta) { $arg = preg_replace('/(["^&|<>()%])/', '^$1', $arg); } return $arg; } public static function supportsColor($output) { if ('Hyper' === getenv('TERM_PROGRAM')) { return true; } if (defined('PHP_WINDOWS_VERSION_BUILD')) { return (function_exists('sapi_windows_vt100_support') && sapi_windows_vt100_support($output)) || false !== getenv('ANSICON') || 'ON' === getenv('ConEmuANSI') || 'xterm' === getenv('TERM'); } if (function_exists('stream_isatty')) { return stream_isatty($output); } if (function_exists('posix_isatty')) { return posix_isatty($output); } $stat = fstat($output); return $stat ? 0020000 === ($stat['mode'] & 0170000) : false; } public static function setEnv($name, $value = false) { $unset = false === $value; if (!putenv($unset ? $name : $name.'='.$value)) { return false; } if ($unset) { unset($_SERVER[$name]); } else { $_SERVER[$name] = $value; } if (false !== stripos((string) ini_get('variables_order'), 'E')) { if ($unset) { unset($_ENV[$name]); } else { $_ENV[$name] = $value; } } return true; } } __DIR__ . '/..' . '/paragonie/random_compat/lib/random.php', '320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php', '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php', '023d27dca8066ef29e6739335ea73bad' => __DIR__ . '/..' . '/symfony/polyfill-php70/bootstrap.php', '25072dd6e2470089de65ae7bf11d3109' => __DIR__ . '/..' . '/symfony/polyfill-php72/bootstrap.php', ); public static $prefixLengthsPsr4 = array ( 'S' => array ( 'Symfony\\Polyfill\\Php72\\' => 23, 'Symfony\\Polyfill\\Php70\\' => 23, 'Symfony\\Polyfill\\Mbstring\\' => 26, 'Symfony\\Polyfill\\Ctype\\' => 23, 'Symfony\\Component\\Stopwatch\\' => 28, 'Symfony\\Component\\Process\\' => 26, 'Symfony\\Component\\OptionsResolver\\' => 34, 'Symfony\\Component\\Finder\\' => 25, 'Symfony\\Component\\Filesystem\\' => 29, 'Symfony\\Component\\EventDispatcher\\' => 34, 'Symfony\\Component\\Debug\\' => 24, 'Symfony\\Component\\Console\\' => 26, ), 'P' => array ( 'Psr\\Log\\' => 8, 'PhpCsFixer\\' => 11, ), 'D' => array ( 'Doctrine\\Common\\Lexer\\' => 22, 'Doctrine\\Common\\Annotations\\' => 28, ), 'C' => array ( 'Composer\\XdebugHandler\\' => 23, 'Composer\\Semver\\' => 16, ), ); public static $prefixDirsPsr4 = array ( 'Symfony\\Polyfill\\Php72\\' => array ( 0 => __DIR__ . '/..' . '/symfony/polyfill-php72', ), 'Symfony\\Polyfill\\Php70\\' => array ( 0 => __DIR__ . '/..' . '/symfony/polyfill-php70', ), 'Symfony\\Polyfill\\Mbstring\\' => array ( 0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring', ), 'Symfony\\Polyfill\\Ctype\\' => array ( 0 => __DIR__ . '/..' . '/symfony/polyfill-ctype', ), 'Symfony\\Component\\Stopwatch\\' => array ( 0 => __DIR__ . '/..' . '/symfony/stopwatch', ), 'Symfony\\Component\\Process\\' => array ( 0 => __DIR__ . '/..' . '/symfony/process', ), 'Symfony\\Component\\OptionsResolver\\' => array ( 0 => __DIR__ . '/..' . '/symfony/options-resolver', ), '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\\Debug\\' => array ( 0 => __DIR__ . '/..' . '/symfony/debug', ), 'Symfony\\Component\\Console\\' => array ( 0 => __DIR__ . '/..' . '/symfony/console', ), 'Psr\\Log\\' => array ( 0 => __DIR__ . '/..' . '/psr/log/Psr/Log', ), 'PhpCsFixer\\' => array ( 0 => __DIR__ . '/../..' . '/src', ), 'Doctrine\\Common\\Lexer\\' => array ( 0 => __DIR__ . '/..' . '/doctrine/lexer/lib/Doctrine/Common/Lexer', ), 'Doctrine\\Common\\Annotations\\' => array ( 0 => __DIR__ . '/..' . '/doctrine/annotations/lib/Doctrine/Common/Annotations', ), 'Composer\\XdebugHandler\\' => array ( 0 => __DIR__ . '/..' . '/composer/xdebug-handler/src', ), 'Composer\\Semver\\' => array ( 0 => __DIR__ . '/..' . '/composer/semver/src', ), ); public static $classMap = array ( 'ArithmeticError' => __DIR__ . '/..' . '/symfony/polyfill-php70/Resources/stubs/ArithmeticError.php', 'AssertionError' => __DIR__ . '/..' . '/symfony/polyfill-php70/Resources/stubs/AssertionError.php', 'DivisionByZeroError' => __DIR__ . '/..' . '/symfony/polyfill-php70/Resources/stubs/DivisionByZeroError.php', 'Error' => __DIR__ . '/..' . '/symfony/polyfill-php70/Resources/stubs/Error.php', 'ParseError' => __DIR__ . '/..' . '/symfony/polyfill-php70/Resources/stubs/ParseError.php', 'PhpCsFixer\\Diff\\GeckoPackages\\DiffOutputBuilder\\ConfigurationException' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/GeckoPackages/DiffOutputBuilder/ConfigurationException.php', 'PhpCsFixer\\Diff\\GeckoPackages\\DiffOutputBuilder\\UnifiedDiffOutputBuilder' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/GeckoPackages/DiffOutputBuilder/UnifiedDiffOutputBuilder.php', 'PhpCsFixer\\Diff\\v1_4\\Chunk' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v1_4/Chunk.php', 'PhpCsFixer\\Diff\\v1_4\\Diff' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v1_4/Diff.php', 'PhpCsFixer\\Diff\\v1_4\\Differ' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v1_4/Differ.php', 'PhpCsFixer\\Diff\\v1_4\\LCS\\LongestCommonSubsequence' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v1_4/LCS/LongestCommonSubsequence.php', 'PhpCsFixer\\Diff\\v1_4\\LCS\\MemoryEfficientImplementation' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v1_4/LCS/MemoryEfficientLongestCommonSubsequenceImplementation.php', 'PhpCsFixer\\Diff\\v1_4\\LCS\\TimeEfficientImplementation' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v1_4/LCS/TimeEfficientLongestCommonSubsequenceImplementation.php', 'PhpCsFixer\\Diff\\v1_4\\Line' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v1_4/Line.php', 'PhpCsFixer\\Diff\\v1_4\\Parser' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v1_4/Parser.php', 'PhpCsFixer\\Diff\\v2_0\\Chunk' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v2_0/Chunk.php', 'PhpCsFixer\\Diff\\v2_0\\Diff' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v2_0/Diff.php', 'PhpCsFixer\\Diff\\v2_0\\Differ' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v2_0/Differ.php', 'PhpCsFixer\\Diff\\v2_0\\Exception' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v2_0/Exception/Exception.php', 'PhpCsFixer\\Diff\\v2_0\\InvalidArgumentException' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v2_0/Exception/InvalidArgumentException.php', 'PhpCsFixer\\Diff\\v2_0\\Line' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v2_0/Line.php', 'PhpCsFixer\\Diff\\v2_0\\LongestCommonSubsequenceCalculator' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v2_0/LongestCommonSubsequenceCalculator.php', 'PhpCsFixer\\Diff\\v2_0\\MemoryEfficientLongestCommonSubsequenceCalculator' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v2_0/MemoryEfficientLongestCommonSubsequenceCalculator.php', 'PhpCsFixer\\Diff\\v2_0\\Output\\AbstractChunkOutputBuilder' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v2_0/Output/AbstractChunkOutputBuilder.php', 'PhpCsFixer\\Diff\\v2_0\\Output\\DiffOnlyOutputBuilder' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v2_0/Output/DiffOnlyOutputBuilder.php', 'PhpCsFixer\\Diff\\v2_0\\Output\\DiffOutputBuilderInterface' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v2_0/Output/DiffOutputBuilderInterface.php', 'PhpCsFixer\\Diff\\v2_0\\Output\\UnifiedDiffOutputBuilder' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v2_0/Output/UnifiedDiffOutputBuilder.php', 'PhpCsFixer\\Diff\\v2_0\\Parser' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v2_0/Parser.php', 'PhpCsFixer\\Diff\\v2_0\\TimeEfficientLongestCommonSubsequenceCalculator' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v2_0/TimeEfficientLongestCommonSubsequenceCalculator.php', 'PhpCsFixer\\Diff\\v3_0\\Chunk' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v3_0/Chunk.php', 'PhpCsFixer\\Diff\\v3_0\\ConfigurationException' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v3_0/Exception/ConfigurationException.php', 'PhpCsFixer\\Diff\\v3_0\\Diff' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v3_0/Diff.php', 'PhpCsFixer\\Diff\\v3_0\\Differ' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v3_0/Differ.php', 'PhpCsFixer\\Diff\\v3_0\\Exception' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v3_0/Exception/Exception.php', 'PhpCsFixer\\Diff\\v3_0\\InvalidArgumentException' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v3_0/Exception/InvalidArgumentException.php', 'PhpCsFixer\\Diff\\v3_0\\Line' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v3_0/Line.php', 'PhpCsFixer\\Diff\\v3_0\\LongestCommonSubsequenceCalculator' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v3_0/LongestCommonSubsequenceCalculator.php', 'PhpCsFixer\\Diff\\v3_0\\MemoryEfficientLongestCommonSubsequenceCalculator' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v3_0/MemoryEfficientLongestCommonSubsequenceCalculator.php', 'PhpCsFixer\\Diff\\v3_0\\Output\\AbstractChunkOutputBuilder' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v3_0/Output/AbstractChunkOutputBuilder.php', 'PhpCsFixer\\Diff\\v3_0\\Output\\DiffOnlyOutputBuilder' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v3_0/Output/DiffOnlyOutputBuilder.php', 'PhpCsFixer\\Diff\\v3_0\\Output\\DiffOutputBuilderInterface' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v3_0/Output/DiffOutputBuilderInterface.php', 'PhpCsFixer\\Diff\\v3_0\\Output\\StrictUnifiedDiffOutputBuilder' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v3_0/Output/StrictUnifiedDiffOutputBuilder.php', 'PhpCsFixer\\Diff\\v3_0\\Output\\UnifiedDiffOutputBuilder' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v3_0/Output/UnifiedDiffOutputBuilder.php', 'PhpCsFixer\\Diff\\v3_0\\Parser' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v3_0/Parser.php', 'PhpCsFixer\\Diff\\v3_0\\TimeEfficientLongestCommonSubsequenceCalculator' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/v3_0/TimeEfficientLongestCommonSubsequenceCalculator.php', 'PhpCsFixer\\Tests\\TestCase' => __DIR__ . '/../..' . '/tests/TestCase.php', 'PhpCsFixer\\Tests\\Test\\AbstractFixerTestCase' => __DIR__ . '/../..' . '/tests/Test/AbstractFixerTestCase.php', 'PhpCsFixer\\Tests\\Test\\AbstractIntegrationCaseFactory' => __DIR__ . '/../..' . '/tests/Test/AbstractIntegrationCaseFactory.php', 'PhpCsFixer\\Tests\\Test\\AbstractIntegrationTestCase' => __DIR__ . '/../..' . '/tests/Test/AbstractIntegrationTestCase.php', 'PhpCsFixer\\Tests\\Test\\Assert\\AssertTokensTrait' => __DIR__ . '/../..' . '/tests/Test/Assert/AssertTokensTrait.php', 'PhpCsFixer\\Tests\\Test\\IntegrationCase' => __DIR__ . '/../..' . '/tests/Test/IntegrationCase.php', 'PhpCsFixer\\Tests\\Test\\IntegrationCaseFactory' => __DIR__ . '/../..' . '/tests/Test/IntegrationCaseFactory.php', 'PhpCsFixer\\Tests\\Test\\IntegrationCaseFactoryInterface' => __DIR__ . '/../..' . '/tests/Test/IntegrationCaseFactoryInterface.php', 'PhpCsFixer\\Tests\\Test\\InternalIntegrationCaseFactory' => __DIR__ . '/../..' . '/tests/Test/InternalIntegrationCaseFactory.php', 'PhpCsFixer\\Tests\\Test\\IsIdenticalConstraint' => __DIR__ . '/../..' . '/tests/Test/IsIdenticalConstraint.php', 'SessionUpdateTimestampHandlerInterface' => __DIR__ . '/..' . '/symfony/polyfill-php70/Resources/stubs/SessionUpdateTimestampHandlerInterface.php', 'TypeError' => __DIR__ . '/..' . '/symfony/polyfill-php70/Resources/stubs/TypeError.php', ); public static function getInitializer(ClassLoader $loader) { return \Closure::bind(function () use ($loader) { $loader->prefixLengthsPsr4 = ComposerStaticInitf7b4295cda722cee0a3ff19a4e630ab7::$prefixLengthsPsr4; $loader->prefixDirsPsr4 = ComposerStaticInitf7b4295cda722cee0a3ff19a4e630ab7::$prefixDirsPsr4; $loader->classMap = ComposerStaticInitf7b4295cda722cee0a3ff19a4e630ab7::$classMap; }, null, ClassLoader::class); } } prefixesPsr0)) { return call_user_func_array('array_merge', $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; } public function addClassMap(array $classMap) { if ($this->classMap) { $this->classMap = array_merge($this->classMap, $classMap); } else { $this->classMap = $classMap; } } 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 ); } } public function addPsr4($prefix, $paths, $prepend = false) { if (!$prefix) { if ($prepend) { $this->fallbackDirsPsr4 = array_merge( (array) $paths, $this->fallbackDirsPsr4 ); } else { $this->fallbackDirsPsr4 = array_merge( $this->fallbackDirsPsr4, (array) $paths ); } } elseif (!isset($this->prefixDirsPsr4[$prefix])) { $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) { $this->prefixDirsPsr4[$prefix] = array_merge( (array) $paths, $this->prefixDirsPsr4[$prefix] ); } else { $this->prefixDirsPsr4[$prefix] = array_merge( $this->prefixDirsPsr4[$prefix], (array) $paths ); } } public function set($prefix, $paths) { if (!$prefix) { $this->fallbackDirsPsr0 = (array) $paths; } else { $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; } } 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; } } public function setUseIncludePath($useIncludePath) { $this->useIncludePath = $useIncludePath; } public function getUseIncludePath() { return $this->useIncludePath; } public function setClassMapAuthoritative($classMapAuthoritative) { $this->classMapAuthoritative = $classMapAuthoritative; } public function isClassMapAuthoritative() { return $this->classMapAuthoritative; } public function setApcuPrefix($apcuPrefix) { $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null; } public function getApcuPrefix() { return $this->apcuPrefix; } public function register($prepend = false) { spl_autoload_register(array($this, 'loadClass'), true, $prepend); } public function unregister() { spl_autoload_unregister(array($this, 'loadClass')); } public function loadClass($class) { if ($file = $this->findFile($class)) { includeFile($file); return true; } } public function findFile($class) { 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'); if (false === $file && defined('HHVM_VERSION')) { $file = $this->findFileWithExtension($class, '.hh'); } if (null !== $this->apcuPrefix) { apcu_add($this->apcuPrefix.$class, $file); } if (false === $file) { $this->missingClasses[$class] = true; } return $file; } private function findFileWithExtension($class, $ext) { $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; } } } } } foreach ($this->fallbackDirsPsr4 as $dir) { if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { return $file; } } if (false !== $pos = strrpos($class, '\\')) { $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); } else { $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; } } } } } foreach ($this->fallbackDirsPsr0 as $dir) { if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { return $file; } } if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { return $file; } return false; } } function includeFile($file) { include $file; } array($vendorDir . '/symfony/polyfill-php72'), 'Symfony\\Polyfill\\Php70\\' => array($vendorDir . '/symfony/polyfill-php70'), 'Symfony\\Polyfill\\Mbstring\\' => array($vendorDir . '/symfony/polyfill-mbstring'), 'Symfony\\Polyfill\\Ctype\\' => array($vendorDir . '/symfony/polyfill-ctype'), 'Symfony\\Component\\Stopwatch\\' => array($vendorDir . '/symfony/stopwatch'), 'Symfony\\Component\\Process\\' => array($vendorDir . '/symfony/process'), 'Symfony\\Component\\OptionsResolver\\' => array($vendorDir . '/symfony/options-resolver'), 'Symfony\\Component\\Finder\\' => array($vendorDir . '/symfony/finder'), 'Symfony\\Component\\Filesystem\\' => array($vendorDir . '/symfony/filesystem'), 'Symfony\\Component\\EventDispatcher\\' => array($vendorDir . '/symfony/event-dispatcher'), 'Symfony\\Component\\Debug\\' => array($vendorDir . '/symfony/debug'), 'Symfony\\Component\\Console\\' => array($vendorDir . '/symfony/console'), 'Psr\\Log\\' => array($vendorDir . '/psr/log/Psr/Log'), 'PhpCsFixer\\' => array($baseDir . '/src'), 'Doctrine\\Common\\Lexer\\' => array($vendorDir . '/doctrine/lexer/lib/Doctrine/Common/Lexer'), 'Doctrine\\Common\\Annotations\\' => array($vendorDir . '/doctrine/annotations/lib/Doctrine/Common/Annotations'), 'Composer\\XdebugHandler\\' => array($vendorDir . '/composer/xdebug-handler/src'), 'Composer\\Semver\\' => array($vendorDir . '/composer/semver/src'), ); $vendorDir . '/paragonie/random_compat/lib/random.php', '320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php', '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php', '023d27dca8066ef29e6739335ea73bad' => $vendorDir . '/symfony/polyfill-php70/bootstrap.php', '25072dd6e2470089de65ae7bf11d3109' => $vendorDir . '/symfony/polyfill-php72/bootstrap.php', ); = 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); if ($useStaticLoader) { require_once __DIR__ . '/autoload_static.php'; call_user_func(\Composer\Autoload\ComposerStaticInitf7b4295cda722cee0a3ff19a4e630ab7::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\ComposerStaticInitf7b4295cda722cee0a3ff19a4e630ab7::$files; } else { $includeFiles = require __DIR__ . '/autoload_files.php'; } foreach ($includeFiles as $fileIdentifier => $file) { composerRequiref7b4295cda722cee0a3ff19a4e630ab7($fileIdentifier, $file); } return $loader; } } function composerRequiref7b4295cda722cee0a3ff19a4e630ab7($fileIdentifier, $file) { if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { require $file; $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; } } normalize($match[2]); $version = $match[1]; } if (preg_match('{^(?:dev-)?(?:master|trunk|default)$}i', $version)) { return '9999999-dev'; } if (stripos($version, 'dev-') === 0) { return 'dev-' . substr($version, 4); } if (preg_match('{^([^,\s+]++)\+[^\s]++$}', $version, $match)) { $version = $match[1]; } if (preg_match('{^v?(\d{1,5})(\.\d++)?(\.\d++)?(\.\d++)?' . self::$modifierRegex . '$}i', $version, $matches)) { $version = $matches[1] . (!empty($matches[2]) ? $matches[2] : '.0') . (!empty($matches[3]) ? $matches[3] : '.0') . (!empty($matches[4]) ? $matches[4] : '.0'); $index = 5; } elseif (preg_match('{^v?(\d{4}(?:[.:-]?\d{2}){1,6}(?:[.:-]?\d{1,3})?)' . self::$modifierRegex . '$}i', $version, $matches)) { $version = preg_replace('{\D}', '.', $matches[1]); $index = 2; } if (isset($index)) { if (!empty($matches[$index])) { if ('stable' === $matches[$index]) { return $version; } $version .= '-' . $this->expandStability($matches[$index]) . (!empty($matches[$index + 1]) ? ltrim($matches[$index + 1], '.-') : ''); } if (!empty($matches[$index + 2])) { $version .= '-dev'; } return $version; } if (preg_match('{(.*?)[.-]?dev$}i', $version, $match)) { try { return $this->normalizeBranch($match[1]); } catch (\Exception $e) { } } $extraMessage = ''; if (preg_match('{ +as +' . preg_quote($version) . '$}', $fullVersion)) { $extraMessage = ' in "' . $fullVersion . '", the alias must be an exact version'; } elseif (preg_match('{^' . preg_quote($version) . ' +as +}', $fullVersion)) { $extraMessage = ' in "' . $fullVersion . '", the alias source must be an exact version, if it is a branch name you should prefix it with dev-'; } throw new \UnexpectedValueException('Invalid version string "' . $version . '"' . $extraMessage); } public function parseNumericAliasPrefix($branch) { if (preg_match('{^(?P(\d++\\.)*\d++)(?:\.x)?-dev$}i', $branch, $matches)) { return $matches['version'] . '.'; } return false; } public function normalizeBranch($name) { $name = trim($name); if (in_array($name, array('master', 'trunk', 'default'))) { return $this->normalize($name); } if (preg_match('{^v?(\d++)(\.(?:\d++|[xX*]))?(\.(?:\d++|[xX*]))?(\.(?:\d++|[xX*]))?$}i', $name, $matches)) { $version = ''; for ($i = 1; $i < 5; ++$i) { $version .= isset($matches[$i]) ? str_replace(array('*', 'X'), 'x', $matches[$i]) : '.x'; } return str_replace('x', '9999999', $version) . '-dev'; } return 'dev-' . $name; } public function parseConstraints($constraints) { $prettyConstraint = $constraints; if (preg_match('{^([^,\s]*?)@(' . implode('|', self::$stabilities) . ')$}i', $constraints, $match)) { $constraints = empty($match[1]) ? '*' : $match[1]; } if (preg_match('{^(dev-[^,\s@]+?|[^,\s@]+?\.x-dev)#.+$}i', $constraints, $match)) { $constraints = $match[1]; } $orConstraints = preg_split('{\s*\|\|?\s*}', trim($constraints)); $orGroups = array(); foreach ($orConstraints as $constraints) { $andConstraints = preg_split('{(?< ,]) *(? 1) { $constraintObjects = array(); foreach ($andConstraints as $constraint) { foreach ($this->parseConstraint($constraint) as $parsedConstraint) { $constraintObjects[] = $parsedConstraint; } } } else { $constraintObjects = $this->parseConstraint($andConstraints[0]); } if (1 === count($constraintObjects)) { $constraint = $constraintObjects[0]; } else { $constraint = new MultiConstraint($constraintObjects); } $orGroups[] = $constraint; } if (1 === count($orGroups)) { $constraint = $orGroups[0]; } elseif (2 === count($orGroups) && $orGroups[0] instanceof MultiConstraint && $orGroups[1] instanceof MultiConstraint && 2 === count($orGroups[0]->getConstraints()) && 2 === count($orGroups[1]->getConstraints()) && ($a = (string) $orGroups[0]) && strpos($a, '[>=') === 0 && (false !== ($posA = strpos($a, '<', 4))) && ($b = (string) $orGroups[1]) && strpos($b, '[>=') === 0 && (false !== ($posB = strpos($b, '<', 4))) && substr($a, $posA + 2, -1) === substr($b, 4, $posB - 5) ) { $constraint = new MultiConstraint(array( new Constraint('>=', substr($a, 4, $posA - 5)), new Constraint('<', substr($b, $posB + 2, -1)), )); } else { $constraint = new MultiConstraint($orGroups, false); } $constraint->setPrettyString($prettyConstraint); return $constraint; } private function parseConstraint($constraint) { if (preg_match('{^([^,\s]+?)@(' . implode('|', self::$stabilities) . ')$}i', $constraint, $match)) { $constraint = $match[1]; if ($match[2] !== 'stable') { $stabilityModifier = $match[2]; } } if (preg_match('{^v?[xX*](\.[xX*])*$}i', $constraint)) { return array(new EmptyConstraint()); } $versionRegex = 'v?(\d++)(?:\.(\d++))?(?:\.(\d++))?(?:\.(\d++))?' . self::$modifierRegex . '(?:\+[^\s]+)?'; if (preg_match('{^~>?' . $versionRegex . '$}i', $constraint, $matches)) { if (strpos($constraint, '~>') === 0) { throw new \UnexpectedValueException( 'Could not parse version constraint ' . $constraint . ': ' . 'Invalid operator "~>", you probably meant to use the "~" operator' ); } if (isset($matches[4]) && '' !== $matches[4] && null !== $matches[4]) { $position = 4; } elseif (isset($matches[3]) && '' !== $matches[3] && null !== $matches[3]) { $position = 3; } elseif (isset($matches[2]) && '' !== $matches[2] && null !== $matches[2]) { $position = 2; } else { $position = 1; } $stabilitySuffix = ''; if (empty($matches[5]) && empty($matches[7])) { $stabilitySuffix .= '-dev'; } $lowVersion = $this->normalize(substr($constraint . $stabilitySuffix, 1)); $lowerBound = new Constraint('>=', $lowVersion); $highPosition = max(1, $position - 1); $highVersion = $this->manipulateVersionString($matches, $highPosition, 1) . '-dev'; $upperBound = new Constraint('<', $highVersion); return array( $lowerBound, $upperBound, ); } if (preg_match('{^\^' . $versionRegex . '($)}i', $constraint, $matches)) { if ('0' !== $matches[1] || '' === $matches[2] || null === $matches[2]) { $position = 1; } elseif ('0' !== $matches[2] || '' === $matches[3] || null === $matches[3]) { $position = 2; } else { $position = 3; } $stabilitySuffix = ''; if (empty($matches[5]) && empty($matches[7])) { $stabilitySuffix .= '-dev'; } $lowVersion = $this->normalize(substr($constraint . $stabilitySuffix, 1)); $lowerBound = new Constraint('>=', $lowVersion); $highVersion = $this->manipulateVersionString($matches, $position, 1) . '-dev'; $upperBound = new Constraint('<', $highVersion); return array( $lowerBound, $upperBound, ); } if (preg_match('{^v?(\d++)(?:\.(\d++))?(?:\.(\d++))?(?:\.[xX*])++$}', $constraint, $matches)) { if (isset($matches[3]) && '' !== $matches[3] && null !== $matches[3]) { $position = 3; } elseif (isset($matches[2]) && '' !== $matches[2] && null !== $matches[2]) { $position = 2; } else { $position = 1; } $lowVersion = $this->manipulateVersionString($matches, $position) . '-dev'; $highVersion = $this->manipulateVersionString($matches, $position, 1) . '-dev'; if ($lowVersion === '0.0.0.0-dev') { return array(new Constraint('<', $highVersion)); } return array( new Constraint('>=', $lowVersion), new Constraint('<', $highVersion), ); } if (preg_match('{^(?P' . $versionRegex . ') +- +(?P' . $versionRegex . ')($)}i', $constraint, $matches)) { $lowStabilitySuffix = ''; if (empty($matches[6]) && empty($matches[8])) { $lowStabilitySuffix = '-dev'; } $lowVersion = $this->normalize($matches['from']); $lowerBound = new Constraint('>=', $lowVersion . $lowStabilitySuffix); $empty = function ($x) { return ($x === 0 || $x === '0') ? false : empty($x); }; if ((!$empty($matches[11]) && !$empty($matches[12])) || !empty($matches[14]) || !empty($matches[16])) { $highVersion = $this->normalize($matches['to']); $upperBound = new Constraint('<=', $highVersion); } else { $highMatch = array('', $matches[10], $matches[11], $matches[12], $matches[13]); $highVersion = $this->manipulateVersionString($highMatch, $empty($matches[11]) ? 1 : 2, 1) . '-dev'; $upperBound = new Constraint('<', $highVersion); } return array( $lowerBound, $upperBound, ); } if (preg_match('{^(<>|!=|>=?|<=?|==?)?\s*(.*)}', $constraint, $matches)) { try { $version = $this->normalize($matches[2]); if (!empty($stabilityModifier) && self::parseStability($version) === 'stable') { $version .= '-' . $stabilityModifier; } elseif ('<' === $matches[1] || '>=' === $matches[1]) { if (!preg_match('/-' . self::$modifierRegex . '$/', strtolower($matches[2]))) { if (strpos($matches[2], 'dev-') !== 0) { $version .= '-dev'; } } } return array(new Constraint($matches[1] ?: '=', $version)); } catch (\Exception $e) { } } $message = 'Could not parse version constraint ' . $constraint; if (isset($e)) { $message .= ': ' . $e->getMessage(); } throw new \UnexpectedValueException($message); } private function manipulateVersionString($matches, $position, $increment = 0, $pad = '0') { for ($i = 4; $i > 0; --$i) { if ($i > $position) { $matches[$i] = $pad; } elseif ($i === $position && $increment) { $matches[$i] += $increment; if ($matches[$i] < 0) { $matches[$i] = $pad; --$position; if ($i === 1) { return null; } } } } return $matches[1] . '.' . $matches[2] . '.' . $matches[3] . '.' . $matches[4]; } private function expandStability($stability) { $stability = strtolower($stability); switch ($stability) { case 'a': return 'alpha'; case 'b': return 'beta'; case 'p': case 'pl': return 'patch'; case 'rc': return 'RC'; default: return $stability; } } } normalize($version)); $parsedConstraints = $versionParser->parseConstraints($constraints); return $parsedConstraints->matches($provider); } public static function satisfiedBy(array $versions, $constraints) { $versions = array_filter($versions, function ($version) use ($constraints) { return Semver::satisfies($version, $constraints); }); return array_values($versions); } public static function sort(array $versions) { return self::usort($versions, self::SORT_ASC); } public static function rsort(array $versions) { return self::usort($versions, self::SORT_DESC); } private static function usort(array $versions, $direction) { if (null === self::$versionParser) { self::$versionParser = new VersionParser(); } $versionParser = self::$versionParser; $normalized = array(); foreach ($versions as $key => $version) { $normalized[] = array($versionParser->normalize($version), $key); } usort($normalized, function (array $left, array $right) use ($direction) { if ($left[0] === $right[0]) { return 0; } if (Comparator::lessThan($left[0], $right[0])) { return -$direction; } return $direction; }); $sorted = array(); foreach ($normalized as $item) { $sorted[] = $versions[$item[1]]; } return $sorted; } } self::OP_EQ, '==' => self::OP_EQ, '<' => self::OP_LT, '<=' => self::OP_LE, '>' => self::OP_GT, '>=' => self::OP_GE, '<>' => self::OP_NE, '!=' => self::OP_NE, ); private static $transOpInt = array( self::OP_EQ => '==', self::OP_LT => '<', self::OP_LE => '<=', self::OP_GT => '>', self::OP_GE => '>=', self::OP_NE => '!=', ); protected $operator; protected $version; protected $prettyString; public function matches(ConstraintInterface $provider) { if ($provider instanceof $this) { return $this->matchSpecific($provider); } return $provider->matches($this); } public function setPrettyString($prettyString) { $this->prettyString = $prettyString; } public function getPrettyString() { if ($this->prettyString) { return $this->prettyString; } return $this->__toString(); } public static function getSupportedOperators() { return array_keys(self::$transOpStr); } public function __construct($operator, $version) { if (!isset(self::$transOpStr[$operator])) { throw new \InvalidArgumentException(sprintf( 'Invalid operator "%s" given, expected one of: %s', $operator, implode(', ', self::getSupportedOperators()) )); } $this->operator = self::$transOpStr[$operator]; $this->version = $version; } public function versionCompare($a, $b, $operator, $compareBranches = false) { if (!isset(self::$transOpStr[$operator])) { throw new \InvalidArgumentException(sprintf( 'Invalid operator "%s" given, expected one of: %s', $operator, implode(', ', self::getSupportedOperators()) )); } $aIsBranch = 'dev-' === substr($a, 0, 4); $bIsBranch = 'dev-' === substr($b, 0, 4); if ($aIsBranch && $bIsBranch) { return $operator === '==' && $a === $b; } if (!$compareBranches && ($aIsBranch || $bIsBranch)) { return false; } return version_compare($a, $b, $operator); } public function matchSpecific(Constraint $provider, $compareBranches = false) { $noEqualOp = str_replace('=', '', self::$transOpInt[$this->operator]); $providerNoEqualOp = str_replace('=', '', self::$transOpInt[$provider->operator]); $isEqualOp = self::OP_EQ === $this->operator; $isNonEqualOp = self::OP_NE === $this->operator; $isProviderEqualOp = self::OP_EQ === $provider->operator; $isProviderNonEqualOp = self::OP_NE === $provider->operator; if ($isNonEqualOp || $isProviderNonEqualOp) { return (!$isEqualOp && !$isProviderEqualOp) || $this->versionCompare($provider->version, $this->version, '!=', $compareBranches); } if ($this->operator !== self::OP_EQ && $noEqualOp === $providerNoEqualOp) { return true; } if ($this->versionCompare($provider->version, $this->version, self::$transOpInt[$this->operator], $compareBranches)) { return !($provider->version === $this->version && self::$transOpInt[$provider->operator] === $providerNoEqualOp && self::$transOpInt[$this->operator] !== $noEqualOp); } return false; } public function __toString() { return self::$transOpInt[$this->operator] . ' ' . $this->version; } } constraints = $constraints; $this->conjunctive = $conjunctive; } public function getConstraints() { return $this->constraints; } public function isConjunctive() { return $this->conjunctive; } public function isDisjunctive() { return !$this->conjunctive; } public function matches(ConstraintInterface $provider) { if (false === $this->conjunctive) { foreach ($this->constraints as $constraint) { if ($constraint->matches($provider)) { return true; } } return false; } foreach ($this->constraints as $constraint) { if (!$constraint->matches($provider)) { return false; } } return true; } public function setPrettyString($prettyString) { $this->prettyString = $prettyString; } public function getPrettyString() { if ($this->prettyString) { return $this->prettyString; } return (string) $this; } public function __toString() { $constraints = array(); foreach ($this->constraints as $constraint) { $constraints[] = (string) $constraint; } return '[' . implode($this->conjunctive ? ' ' : ' || ', $constraints) . ']'; } } prettyString = $prettyString; } public function getPrettyString() { if ($this->prettyString) { return $this->prettyString; } return (string) $this; } public function __toString() { return '[]'; } } matchSpecific($provider); } return $provider->matches($this); } public function setPrettyString($prettyString) { $this->prettyString = $prettyString; } public function getPrettyString() { if ($this->prettyString) { return $this->prettyString; } return $this->__toString(); } } ', $version2); } public static function greaterThanOrEqualTo($version1, $version2) { return self::compare($version1, '>=', $version2); } public static function lessThan($version1, $version2) { return self::compare($version1, '<', $version2); } public static function lessThanOrEqualTo($version1, $version2) { return self::compare($version1, '<=', $version2); } public static function equalTo($version1, $version2) { return self::compare($version1, '==', $version2); } public static function notEqualTo($version1, $version2) { return self::compare($version1, '!=', $version2); } public static function compare($version1, $operator, $version2) { $constraint = new Constraint($operator, $version2); return $constraint->matches(new Constraint('==', $version1)); } } ' : \gettype($value).'#'.$value) ), $code, $previous ); } } 3, 'collapseRanges' => true, 'fromFile' => null, 'fromFileDate' => null, 'toFile' => null, 'toFileDate' => null, 'commonLineThreshold' => 6, ]; public function __construct(array $options = []) { $options = \array_merge(self::$default, $options); if (!\is_bool($options['collapseRanges'])) { throw new ConfigurationException('collapseRanges', 'a bool', $options['collapseRanges']); } if (!\is_int($options['contextLines']) || $options['contextLines'] < 0) { throw new ConfigurationException('contextLines', 'an int >= 0', $options['contextLines']); } if (!\is_int($options['commonLineThreshold']) || $options['commonLineThreshold'] < 1) { throw new ConfigurationException('commonLineThreshold', 'an int > 0', $options['commonLineThreshold']); } foreach (['fromFile', 'toFile'] as $option) { if (!\is_string($options[$option])) { throw new ConfigurationException($option, 'a string', $options[$option]); } } foreach (['fromFileDate', 'toFileDate'] as $option) { if (null !== $options[$option] && !\is_string($options[$option])) { throw new ConfigurationException($option, 'a string or ', $options[$option]); } } $this->header = \sprintf( "--- %s%s\n+++ %s%s\n", $options['fromFile'], null === $options['fromFileDate'] ? '' : "\t".$options['fromFileDate'], $options['toFile'], null === $options['toFileDate'] ? '' : "\t".$options['toFileDate'] ); $this->collapseRanges = $options['collapseRanges']; $this->commonLineThreshold = $options['commonLineThreshold']; $this->contextLines = $options['contextLines']; } public function getDiff(array $diff) { if (0 === \count($diff)) { return ''; } $this->changed = false; $buffer = \fopen('php://memory', 'r+b'); \fwrite($buffer, $this->header); $this->writeDiffHunks($buffer, $diff); $diff = \stream_get_contents($buffer, -1, 0); \fclose($buffer); if (!$this->changed) { return ''; } return $diff; } private function writeDiffHunks($output, array $diff) { $upperLimit = \count($diff); if (0 === $diff[$upperLimit - 1][1]) { $lc = \substr($diff[$upperLimit - 1][0], -1); if ("\n" !== $lc) { \array_splice($diff, $upperLimit, 0, [["\n\\ No newline at end of file\n", self::$noNewlineAtOEFid]]); } } else { $toFind = [1 => true, 2 => true]; for ($i = $upperLimit - 1; $i >= 0; --$i) { if (isset($toFind[$diff[$i][1]])) { unset($toFind[$diff[$i][1]]); $lc = \substr($diff[$i][0], -1); if ("\n" !== $lc) { \array_splice($diff, $i + 1, 0, [["\n\\ No newline at end of file\n", self::$noNewlineAtOEFid]]); } if (!\count($toFind)) { break; } } } } $cutOff = \max($this->commonLineThreshold, $this->contextLines); $hunkCapture = false; $sameCount = $toRange = $fromRange = 0; $toStart = $fromStart = 1; foreach ($diff as $i => $entry) { if (0 === $entry[1]) { if (false === $hunkCapture) { ++$fromStart; ++$toStart; continue; } ++$sameCount; ++$toRange; ++$fromRange; if ($sameCount === $cutOff) { $contextStartOffset = $hunkCapture - $this->contextLines < 0 ? $hunkCapture : $this->contextLines ; $contextEndOffset = $i + $this->contextLines >= \count($diff) ? \count($diff) - $i : $this->contextLines ; $this->writeHunk( $diff, $hunkCapture - $contextStartOffset, $i - $cutOff + $contextEndOffset + 1, $fromStart - $contextStartOffset, $fromRange - $cutOff + $contextStartOffset + $contextEndOffset, $toStart - $contextStartOffset, $toRange - $cutOff + $contextStartOffset + $contextEndOffset, $output ); $fromStart += $fromRange; $toStart += $toRange; $hunkCapture = false; $sameCount = $toRange = $fromRange = 0; } continue; } $sameCount = 0; if ($entry[1] === self::$noNewlineAtOEFid) { continue; } $this->changed = true; if (false === $hunkCapture) { $hunkCapture = $i; } if (1 === $entry[1]) { ++$toRange; } if (2 === $entry[1]) { ++$fromRange; } } if (false !== $hunkCapture) { $contextStartOffset = $hunkCapture - $this->contextLines < 0 ? $hunkCapture : $this->contextLines ; $this->writeHunk( $diff, $hunkCapture - $contextStartOffset, \count($diff), $fromStart - $contextStartOffset, $fromRange + $contextStartOffset, $toStart - $contextStartOffset, $toRange + $contextStartOffset, $output ); } } private function writeHunk( array $diff, $diffStartIndex, $diffEndIndex, $fromStart, $fromRange, $toStart, $toRange, $output ) { \fwrite($output, '@@ -'.$fromStart); if (!$this->collapseRanges || 1 !== $fromRange) { \fwrite($output, ','.$fromRange); } \fwrite($output, ' +'.$toStart); if (!$this->collapseRanges || 1 !== $toRange) { \fwrite($output, ','.$toRange); } \fwrite($output, " @@\n"); for ($i = $diffStartIndex; $i < $diffEndIndex; ++$i) { if ($diff[$i][1] === 1) { $this->changed = true; \fwrite($output, '+'.$diff[$i][0]); } elseif ($diff[$i][1] === 2) { $this->changed = true; \fwrite($output, '-'.$diff[$i][0]); } elseif ($diff[$i][1] === 0) { \fwrite($output, ' '.$diff[$i][0]); } elseif ($diff[$i][1] === self::$noNewlineAtOEFid) { $this->changed = true; \fwrite($output, $diff[$i][0]); } } } } from = $from; $this->to = $to; $this->chunks = $chunks; } public function getFrom() { return $this->from; } public function getTo() { return $this->to; } public function getChunks() { return $this->chunks; } public function setChunks(array $chunks) { $this->chunks = $chunks; } } = $lineThreshold) { $commonChunks[$chunkStart] = $chunkStart + $chunkSize; } $capturing = false; } } if ($capturing !== false && $chunkSize >= $lineThreshold) { $commonChunks[$chunkStart] = $chunkStart + $chunkSize; } return $commonChunks; } } header = $header; } public function getDiff(array $diff) { $buffer = \fopen('php://memory', 'r+b'); if ('' !== $this->header) { \fwrite($buffer, $this->header); if ("\n" !== \substr($this->header, -1, 1)) { \fwrite($buffer, "\n"); } } foreach ($diff as $diffEntry) { if ($diffEntry[1] === Differ::ADDED) { \fwrite($buffer, '+' . $diffEntry[0]); } elseif ($diffEntry[1] === Differ::REMOVED) { \fwrite($buffer, '-' . $diffEntry[0]); } elseif ($diffEntry[1] === Differ::DIFF_LINE_END_WARNING) { \fwrite($buffer, ' ' . $diffEntry[0]); continue; } else { continue; } $lc = \substr($diffEntry[0], -1); if ($lc !== "\n" && $lc !== "\r") { \fwrite($buffer, "\n"); } } $diff = \stream_get_contents($buffer, -1, 0); \fclose($buffer); return $diff; } } true, 'commonLineThreshold' => 6, 'contextLines' => 3, 'fromFile' => null, 'fromFileDate' => null, 'toFile' => null, 'toFileDate' => null, ]; public function __construct(array $options = []) { $options = \array_merge(self::$default, $options); if (!\is_bool($options['collapseRanges'])) { throw new ConfigurationException('collapseRanges', 'a bool', $options['collapseRanges']); } if (!\is_int($options['contextLines']) || $options['contextLines'] < 0) { throw new ConfigurationException('contextLines', 'an int >= 0', $options['contextLines']); } if (!\is_int($options['commonLineThreshold']) || $options['commonLineThreshold'] <= 0) { throw new ConfigurationException('commonLineThreshold', 'an int > 0', $options['commonLineThreshold']); } foreach (['fromFile', 'toFile'] as $option) { if (!\is_string($options[$option])) { throw new ConfigurationException($option, 'a string', $options[$option]); } } foreach (['fromFileDate', 'toFileDate'] as $option) { if (null !== $options[$option] && !\is_string($options[$option])) { throw new ConfigurationException($option, 'a string or ', $options[$option]); } } $this->header = \sprintf( "--- %s%s\n+++ %s%s\n", $options['fromFile'], null === $options['fromFileDate'] ? '' : "\t" . $options['fromFileDate'], $options['toFile'], null === $options['toFileDate'] ? '' : "\t" . $options['toFileDate'] ); $this->collapseRanges = $options['collapseRanges']; $this->commonLineThreshold = $options['commonLineThreshold']; $this->contextLines = $options['contextLines']; } public function getDiff(array $diff) { if (0 === \count($diff)) { return ''; } $this->changed = false; $buffer = \fopen('php://memory', 'r+b'); \fwrite($buffer, $this->header); $this->writeDiffHunks($buffer, $diff); if (!$this->changed) { \fclose($buffer); return ''; } $diff = \stream_get_contents($buffer, -1, 0); \fclose($buffer); $last = \substr($diff, -1); return "\n" !== $last && "\r" !== $last ? $diff . "\n" : $diff ; } private function writeDiffHunks($output, array $diff) { $upperLimit = \count($diff); if (0 === $diff[$upperLimit - 1][1]) { $lc = \substr($diff[$upperLimit - 1][0], -1); if ("\n" !== $lc) { \array_splice($diff, $upperLimit, 0, [["\n\\ No newline at end of file\n", Differ::NO_LINE_END_EOF_WARNING]]); } } else { $toFind = [1 => true, 2 => true]; for ($i = $upperLimit - 1; $i >= 0; --$i) { if (isset($toFind[$diff[$i][1]])) { unset($toFind[$diff[$i][1]]); $lc = \substr($diff[$i][0], -1); if ("\n" !== $lc) { \array_splice($diff, $i + 1, 0, [["\n\\ No newline at end of file\n", Differ::NO_LINE_END_EOF_WARNING]]); } if (!\count($toFind)) { break; } } } } $cutOff = \max($this->commonLineThreshold, $this->contextLines); $hunkCapture = false; $sameCount = $toRange = $fromRange = 0; $toStart = $fromStart = 1; foreach ($diff as $i => $entry) { if (0 === $entry[1]) { if (false === $hunkCapture) { ++$fromStart; ++$toStart; continue; } ++$sameCount; ++$toRange; ++$fromRange; if ($sameCount === $cutOff) { $contextStartOffset = ($hunkCapture - $this->contextLines) < 0 ? $hunkCapture : $this->contextLines ; $this->writeHunk( $diff, $hunkCapture - $contextStartOffset, $i - $cutOff + $this->contextLines + 1, $fromStart - $contextStartOffset, $fromRange - $cutOff + $contextStartOffset + $this->contextLines, $toStart - $contextStartOffset, $toRange - $cutOff + $contextStartOffset + $this->contextLines, $output ); $fromStart += $fromRange; $toStart += $toRange; $hunkCapture = false; $sameCount = $toRange = $fromRange = 0; } continue; } $sameCount = 0; if ($entry[1] === Differ::NO_LINE_END_EOF_WARNING) { continue; } $this->changed = true; if (false === $hunkCapture) { $hunkCapture = $i; } if (Differ::ADDED === $entry[1]) { ++$toRange; } if (Differ::REMOVED === $entry[1]) { ++$fromRange; } } if (false === $hunkCapture) { return; } $contextStartOffset = $hunkCapture - $this->contextLines < 0 ? $hunkCapture : $this->contextLines ; $contextEndOffset = \min($sameCount, $this->contextLines); $fromRange -= $sameCount; $toRange -= $sameCount; $this->writeHunk( $diff, $hunkCapture - $contextStartOffset, $i - $sameCount + $contextEndOffset + 1, $fromStart - $contextStartOffset, $fromRange + $contextStartOffset + $contextEndOffset, $toStart - $contextStartOffset, $toRange + $contextStartOffset + $contextEndOffset, $output ); } private function writeHunk( array $diff, $diffStartIndex, $diffEndIndex, $fromStart, $fromRange, $toStart, $toRange, $output ) { \fwrite($output, '@@ -' . $fromStart); if (!$this->collapseRanges || 1 !== $fromRange) { \fwrite($output, ',' . $fromRange); } \fwrite($output, ' +' . $toStart); if (!$this->collapseRanges || 1 !== $toRange) { \fwrite($output, ',' . $toRange); } \fwrite($output, " @@\n"); for ($i = $diffStartIndex; $i < $diffEndIndex; ++$i) { if ($diff[$i][1] === Differ::ADDED) { $this->changed = true; \fwrite($output, '+' . $diff[$i][0]); } elseif ($diff[$i][1] === Differ::REMOVED) { $this->changed = true; \fwrite($output, '-' . $diff[$i][0]); } elseif ($diff[$i][1] === Differ::OLD) { \fwrite($output, ' ' . $diff[$i][0]); } elseif ($diff[$i][1] === Differ::NO_LINE_END_EOF_WARNING) { $this->changed = true; \fwrite($output, $diff[$i][0]); } } } } header = $header; $this->addLineNumbers = $addLineNumbers; } public function getDiff(array $diff) { $buffer = \fopen('php://memory', 'r+b'); if ('' !== $this->header) { \fwrite($buffer, $this->header); if ("\n" !== \substr($this->header, -1, 1)) { \fwrite($buffer, "\n"); } } if (0 !== \count($diff)) { $this->writeDiffHunks($buffer, $diff); } $diff = \stream_get_contents($buffer, -1, 0); \fclose($buffer); $last = \substr($diff, -1); return "\n" !== $last && "\r" !== $last ? $diff . "\n" : $diff ; } private function writeDiffHunks($output, array $diff) { $upperLimit = \count($diff); if (0 === $diff[$upperLimit - 1][1]) { $lc = \substr($diff[$upperLimit - 1][0], -1); if ("\n" !== $lc) { \array_splice($diff, $upperLimit, 0, [["\n\\ No newline at end of file\n", Differ::NO_LINE_END_EOF_WARNING]]); } } else { $toFind = [1 => true, 2 => true]; for ($i = $upperLimit - 1; $i >= 0; --$i) { if (isset($toFind[$diff[$i][1]])) { unset($toFind[$diff[$i][1]]); $lc = \substr($diff[$i][0], -1); if ("\n" !== $lc) { \array_splice($diff, $i + 1, 0, [["\n\\ No newline at end of file\n", Differ::NO_LINE_END_EOF_WARNING]]); } if (!\count($toFind)) { break; } } } } $cutOff = \max($this->commonLineThreshold, $this->contextLines); $hunkCapture = false; $sameCount = $toRange = $fromRange = 0; $toStart = $fromStart = 1; foreach ($diff as $i => $entry) { if (0 === $entry[1]) { if (false === $hunkCapture) { ++$fromStart; ++$toStart; continue; } ++$sameCount; ++$toRange; ++$fromRange; if ($sameCount === $cutOff) { $contextStartOffset = ($hunkCapture - $this->contextLines) < 0 ? $hunkCapture : $this->contextLines ; $this->writeHunk( $diff, $hunkCapture - $contextStartOffset, $i - $cutOff + $this->contextLines + 1, $fromStart - $contextStartOffset, $fromRange - $cutOff + $contextStartOffset + $this->contextLines, $toStart - $contextStartOffset, $toRange - $cutOff + $contextStartOffset + $this->contextLines, $output ); $fromStart += $fromRange; $toStart += $toRange; $hunkCapture = false; $sameCount = $toRange = $fromRange = 0; } continue; } $sameCount = 0; if ($entry[1] === Differ::NO_LINE_END_EOF_WARNING) { continue; } if (false === $hunkCapture) { $hunkCapture = $i; } if (Differ::ADDED === $entry[1]) { ++$toRange; } if (Differ::REMOVED === $entry[1]) { ++$fromRange; } } if (false === $hunkCapture) { return; } $contextStartOffset = $hunkCapture - $this->contextLines < 0 ? $hunkCapture : $this->contextLines ; $contextEndOffset = \min($sameCount, $this->contextLines); $fromRange -= $sameCount; $toRange -= $sameCount; $this->writeHunk( $diff, $hunkCapture - $contextStartOffset, $i - $sameCount + $contextEndOffset + 1, $fromStart - $contextStartOffset, $fromRange + $contextStartOffset + $contextEndOffset, $toStart - $contextStartOffset, $toRange + $contextStartOffset + $contextEndOffset, $output ); } private function writeHunk( array $diff, $diffStartIndex, $diffEndIndex, $fromStart, $fromRange, $toStart, $toRange, $output ) { if ($this->addLineNumbers) { \fwrite($output, '@@ -' . $fromStart); if (!$this->collapseRanges || 1 !== $fromRange) { \fwrite($output, ',' . $fromRange); } \fwrite($output, ' +' . $toStart); if (!$this->collapseRanges || 1 !== $toRange) { \fwrite($output, ',' . $toRange); } \fwrite($output, " @@\n"); } else { \fwrite($output, "@@ @@\n"); } for ($i = $diffStartIndex; $i < $diffEndIndex; ++$i) { if ($diff[$i][1] === Differ::ADDED) { \fwrite($output, '+' . $diff[$i][0]); } elseif ($diff[$i][1] === Differ::REMOVED) { \fwrite($output, '-' . $diff[$i][0]); } elseif ($diff[$i][1] === Differ::OLD) { \fwrite($output, ' ' . $diff[$i][0]); } elseif ($diff[$i][1] === Differ::NO_LINE_END_EOF_WARNING) { \fwrite($output, "\n"); } else { \fwrite($output, ' ' . $diff[$i][0]); } } } } outputBuilder = $outputBuilder; } elseif (null === $outputBuilder) { $this->outputBuilder = new UnifiedDiffOutputBuilder; } elseif (\is_string($outputBuilder)) { $this->outputBuilder = new UnifiedDiffOutputBuilder($outputBuilder); } else { throw new InvalidArgumentException( \sprintf( 'Expected builder to be an instance of DiffOutputBuilderInterface, or a string, got %s.', \is_object($outputBuilder) ? 'instance of "' . \get_class($outputBuilder) . '"' : \gettype($outputBuilder) . ' "' . $outputBuilder . '"' ) ); } } public function diff($from, $to, LongestCommonSubsequenceCalculator $lcs = null) { $diff = $this->diffToArray( $this->normalizeDiffInput($from), $this->normalizeDiffInput($to), $lcs ); return $this->outputBuilder->getDiff($diff); } public function diffToArray($from, $to, LongestCommonSubsequenceCalculator $lcs = null) { if (\is_string($from)) { $from = $this->splitStringByLines($from); } elseif (!\is_array($from)) { throw new InvalidArgumentException('"from" must be an array or string.'); } if (\is_string($to)) { $to = $this->splitStringByLines($to); } elseif (!\is_array($to)) { throw new InvalidArgumentException('"to" must be an array or string.'); } list($from, $to, $start, $end) = self::getArrayDiffParted($from, $to); if ($lcs === null) { $lcs = $this->selectLcsImplementation($from, $to); } $common = $lcs->calculate(\array_values($from), \array_values($to)); $diff = []; foreach ($start as $token) { $diff[] = [$token, self::OLD]; } \reset($from); \reset($to); foreach ($common as $token) { while (($fromToken = \reset($from)) !== $token) { $diff[] = [\array_shift($from), self::REMOVED]; } while (($toToken = \reset($to)) !== $token) { $diff[] = [\array_shift($to), self::ADDED]; } $diff[] = [$token, self::OLD]; \array_shift($from); \array_shift($to); } while (($token = \array_shift($from)) !== null) { $diff[] = [$token, self::REMOVED]; } while (($token = \array_shift($to)) !== null) { $diff[] = [$token, self::ADDED]; } foreach ($end as $token) { $diff[] = [$token, self::OLD]; } if ($this->detectUnmatchedLineEndings($diff)) { \array_unshift($diff, ["#Warnings contain different line endings!\n", self::DIFF_LINE_END_WARNING]); } return $diff; } private function normalizeDiffInput($input) { if (!\is_array($input) && !\is_string($input)) { return (string) $input; } return $input; } private function splitStringByLines($input) { return \preg_split('/(.*\R)/', $input, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); } private function selectLcsImplementation(array $from, array $to) { $memoryLimit = 100 * 1024 * 1024; if ($this->calculateEstimatedFootprint($from, $to) > $memoryLimit) { return new MemoryEfficientLongestCommonSubsequenceCalculator; } return new TimeEfficientLongestCommonSubsequenceCalculator; } private function calculateEstimatedFootprint(array $from, array $to) { $itemSize = PHP_INT_SIZE === 4 ? 76 : 144; return $itemSize * \min(\count($from), \count($to)) ** 2; } private function detectUnmatchedLineEndings(array $diff) { $newLineBreaks = ['' => true]; $oldLineBreaks = ['' => true]; foreach ($diff as $entry) { if (self::OLD === $entry[1]) { $ln = $this->getLinebreak($entry[0]); $oldLineBreaks[$ln] = true; $newLineBreaks[$ln] = true; } elseif (self::ADDED === $entry[1]) { $newLineBreaks[$this->getLinebreak($entry[0])] = true; } elseif (self::REMOVED === $entry[1]) { $oldLineBreaks[$this->getLinebreak($entry[0])] = true; } } if (['' => true] === $newLineBreaks || ['' => true] === $oldLineBreaks) { return false; } foreach ($newLineBreaks as $break => $set) { if (!isset($oldLineBreaks[$break])) { return true; } } foreach ($oldLineBreaks as $break => $set) { if (!isset($newLineBreaks[$break])) { return true; } } return false; } private function getLinebreak($line) { if (!\is_string($line)) { return ''; } $lc = \substr($line, -1); if ("\r" === $lc) { return "\r"; } if ("\n" !== $lc) { return ''; } if ("\r\n" === \substr($line, -2)) { return "\r\n"; } return "\n"; } private static function getArrayDiffParted(array &$from, array &$to) { $start = []; $end = []; \reset($to); foreach ($from as $k => $v) { $toK = \key($to); if ($toK === $k && $v === $to[$k]) { $start[$k] = $v; unset($from[$k], $to[$k]); } else { break; } } \end($from); \end($to); do { $fromK = \key($from); $toK = \key($to); if (null === $fromK || null === $toK || \current($from) !== \current($to)) { break; } \prev($from); \prev($to); $end = [$fromK => $from[$fromK]] + $end; unset($from[$fromK], $to[$toK]); } while (true); return [$from, $to, $start, $end]; } } 0 && $j > 0) { if ($from[$i - 1] === $to[$j - 1]) { $common[] = $from[$i - 1]; --$i; --$j; } else { $o = ($j * $width) + $i; if ($matrix[$o - $width] > $matrix[$o - 1]) { --$j; } else { --$i; } } } return \array_reverse($common); } } \\S+))', $lines[$i], $fromMatch) && \preg_match('(^\\+\\+\\+\\s+(?P\\S+))', $lines[$i + 1], $toMatch)) { if ($diff !== null) { $this->parseFileDiff($diff, $collected); $diffs[] = $diff; $collected = []; } $diff = new Diff($fromMatch['file'], $toMatch['file']); ++$i; } else { if (\preg_match('/^(?:diff --git |index [\da-f\.]+|[+-]{3} [ab])/', $lines[$i])) { continue; } $collected[] = $lines[$i]; } } if ($diff !== null && \count($collected)) { $this->parseFileDiff($diff, $collected); $diffs[] = $diff; } return $diffs; } private function parseFileDiff(Diff $diff, array $lines) { $chunks = []; $chunk = null; foreach ($lines as $line) { if (\preg_match('/^@@\s+-(?P\d+)(?:,\s*(?P\d+))?\s+\+(?P\d+)(?:,\s*(?P\d+))?\s+@@/', $line, $match)) { $chunk = new Chunk( (int) $match['start'], isset($match['startrange']) ? \max(1, (int) $match['startrange']) : 1, (int) $match['end'], isset($match['endrange']) ? \max(1, (int) $match['endrange']) : 1 ); $chunks[] = $chunk; $diffLines = []; continue; } if (\preg_match('/^(?P[+ -])?(?P.*)/', $line, $match)) { $type = Line::UNCHANGED; if ($match['type'] === '+') { $type = Line::ADDED; } elseif ($match['type'] === '-') { $type = Line::REMOVED; } $diffLines[] = new Line($type, $match['line']); if (null !== $chunk) { $chunk->setLines($diffLines); } } } $diff->setChunks($chunks); } } ' : \gettype($value) . '#' . $value) ), $code, $previous ); } } start = $start; $this->startRange = $startRange; $this->end = $end; $this->endRange = $endRange; $this->lines = $lines; } public function getStart() { return $this->start; } public function getStartRange() { return $this->startRange; } public function getEnd() { return $this->end; } public function getEndRange() { return $this->endRange; } public function getLines() { return $this->lines; } public function setLines(array $lines) { $this->lines = $lines; } } type = $type; $this->content = $content; } public function getContent() { return $this->content; } public function getType() { return $this->type; } } length($fromStart, $to); $llE = $this->length(\array_reverse($fromEnd), \array_reverse($to)); $jMax = 0; $max = 0; for ($j = 0; $j <= $cTo; $j++) { $m = $llB[$j] + $llE[$cTo - $j]; if ($m >= $max) { $max = $m; $jMax = $j; } } $toStart = \array_slice($to, 0, $jMax); $toEnd = \array_slice($to, $jMax); return \array_merge( $this->calculate($fromStart, $toStart), $this->calculate($fromEnd, $toEnd) ); } private function length(array $from, array $to) { $current = \array_fill(0, \count($to) + 1, 0); $cFrom = \count($from); $cTo = \count($to); for ($i = 0; $i < $cFrom; $i++) { $prev = $current; for ($j = 0; $j < $cTo; $j++) { if ($from[$i] === $to[$j]) { $current[$j + 1] = $prev[$j] + 1; } else { $current[$j + 1] = \max($current[$j], $prev[$j + 1]); } } } return $current; } } from = $from; $this->to = $to; $this->chunks = $chunks; } public function getFrom() { return $this->from; } public function getTo() { return $this->to; } public function getChunks() { return $this->chunks; } public function setChunks(array $chunks) { $this->chunks = $chunks; } } = $lineThreshold) { $commonChunks[$chunkStart] = $chunkStart + $chunkSize; } $capturing = false; } } if ($capturing !== false && $chunkSize >= $lineThreshold) { $commonChunks[$chunkStart] = $chunkStart + $chunkSize; } return $commonChunks; } } header = $header; } public function getDiff(array $diff) { $buffer = \fopen('php://memory', 'r+b'); if ('' !== $this->header) { \fwrite($buffer, $this->header); if ("\n" !== \substr($this->header, -1, 1)) { \fwrite($buffer, "\n"); } } foreach ($diff as $diffEntry) { if ($diffEntry[1] === 1 ) { \fwrite($buffer, '+' . $diffEntry[0]); } elseif ($diffEntry[1] === 2 ) { \fwrite($buffer, '-' . $diffEntry[0]); } elseif ($diffEntry[1] === 3 ) { \fwrite($buffer, ' ' . $diffEntry[0]); continue; } else { continue; } $lc = \substr($diffEntry[0], -1); if ($lc !== "\n" && $lc !== "\r") { \fwrite($buffer, "\n"); } } $diff = \stream_get_contents($buffer, -1, 0); \fclose($buffer); return $diff; } } header = $header; $this->addLineNumbers = $addLineNumbers; } public function getDiff(array $diff) { $buffer = \fopen('php://memory', 'r+b'); if ('' !== $this->header) { \fwrite($buffer, $this->header); if ("\n" !== \substr($this->header, -1, 1)) { \fwrite($buffer, "\n"); } } $this->writeDiffChunked($buffer, $diff, $this->getCommonChunks($diff)); $diff = \stream_get_contents($buffer, -1, 0); \fclose($buffer); return $diff; } private function writeDiffChunked($output, array $diff, array $old) { $upperLimit = \count($diff); $start = 0; $fromStart = 0; $toStart = 0; if (\count($old)) { \reset($old); do { $commonStart = \key($old); $commonEnd = \current($old); if ($commonStart !== $start) { list($fromRange, $toRange) = $this->getChunkRange($diff, $start, $commonStart); $this->writeChunk($output, $diff, $start, $commonStart, $fromStart, $fromRange, $toStart, $toRange); $fromStart += $fromRange; $toStart += $toRange; } $start = $commonEnd + 1; $commonLength = $commonEnd - $commonStart + 1; $fromStart += $commonLength; $toStart += $commonLength; } while (false !== \next($old)); \end($old); $tmp = \key($old); \reset($old); if ($old[$tmp] === $upperLimit - 1) { $upperLimit = $tmp; } } if ($start < $upperLimit - 1) { do { --$upperLimit; } while (isset($diff[$upperLimit][1]) && $diff[$upperLimit][1] === 0); ++$upperLimit; list($fromRange, $toRange) = $this->getChunkRange($diff, $start, $upperLimit); $this->writeChunk($output, $diff, $start, $upperLimit, $fromStart, $fromRange, $toStart, $toRange); } } private function writeChunk( $output, array $diff, $diffStartIndex, $diffEndIndex, $fromStart, $fromRange, $toStart, $toRange ) { if ($this->addLineNumbers) { \fwrite($output, '@@ -' . (1 + $fromStart)); if ($fromRange !== 1) { \fwrite($output, ',' . $fromRange); } \fwrite($output, ' +' . (1 + $toStart)); if ($toRange !== 1) { \fwrite($output, ',' . $toRange); } \fwrite($output, " @@\n"); } else { \fwrite($output, "@@ @@\n"); } for ($i = $diffStartIndex; $i < $diffEndIndex; ++$i) { if ($diff[$i][1] === 1 ) { \fwrite($output, '+' . $diff[$i][0]); } elseif ($diff[$i][1] === 2 ) { \fwrite($output, '-' . $diff[$i][0]); } else { \fwrite($output, ' ' . $diff[$i][0]); } $lc = \substr($diff[$i][0], -1); if ($lc !== "\n" && $lc !== "\r") { \fwrite($output, "\n"); } } } private function getChunkRange(array $diff, $diffStartIndex, $diffEndIndex) { $toRange = 0; $fromRange = 0; for ($i = $diffStartIndex; $i < $diffEndIndex; ++$i) { if ($diff[$i][1] === 1) { ++$toRange; } elseif ($diff[$i][1] === 2) { ++$fromRange; } elseif ($diff[$i][1] === 0) { ++$fromRange; ++$toRange; } } return [$fromRange, $toRange]; } } outputBuilder = $outputBuilder; } elseif (null === $outputBuilder) { $this->outputBuilder = new UnifiedDiffOutputBuilder; } elseif (\is_string($outputBuilder)) { $this->outputBuilder = new UnifiedDiffOutputBuilder($outputBuilder); } else { throw new InvalidArgumentException( \sprintf( 'Expected builder to be an instance of DiffOutputBuilderInterface, or a string, got %s.', \is_object($outputBuilder) ? 'instance of "' . \get_class($outputBuilder) . '"' : \gettype($outputBuilder) . ' "' . $outputBuilder . '"' ) ); } } public function diff($from, $to, LongestCommonSubsequenceCalculator $lcs = null) { $from = $this->validateDiffInput($from); $to = $this->validateDiffInput($to); $diff = $this->diffToArray($from, $to, $lcs); return $this->outputBuilder->getDiff($diff); } private function validateDiffInput($input) { if (!\is_array($input) && !\is_string($input)) { return (string) $input; } return $input; } public function diffToArray($from, $to, LongestCommonSubsequenceCalculator $lcs = null) { if (\is_string($from)) { $from = $this->splitStringByLines($from); } elseif (!\is_array($from)) { throw new \InvalidArgumentException('"from" must be an array or string.'); } if (\is_string($to)) { $to = $this->splitStringByLines($to); } elseif (!\is_array($to)) { throw new \InvalidArgumentException('"to" must be an array or string.'); } list($from, $to, $start, $end) = self::getArrayDiffParted($from, $to); if ($lcs === null) { $lcs = $this->selectLcsImplementation($from, $to); } $common = $lcs->calculate(\array_values($from), \array_values($to)); $diff = []; foreach ($start as $token) { $diff[] = [$token, 0 ]; } \reset($from); \reset($to); foreach ($common as $token) { while (($fromToken = \reset($from)) !== $token) { $diff[] = [\array_shift($from), 2 ]; } while (($toToken = \reset($to)) !== $token) { $diff[] = [\array_shift($to), 1 ]; } $diff[] = [$token, 0 ]; \array_shift($from); \array_shift($to); } while (($token = \array_shift($from)) !== null) { $diff[] = [$token, 2 ]; } while (($token = \array_shift($to)) !== null) { $diff[] = [$token, 1 ]; } foreach ($end as $token) { $diff[] = [$token, 0 ]; } if ($this->detectUnmatchedLineEndings($diff)) { \array_unshift($diff, ["#Warnings contain different line endings!\n", 3]); } return $diff; } private function splitStringByLines($input) { return \preg_split('/(.*\R)/', $input, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); } private function selectLcsImplementation(array $from, array $to) { $memoryLimit = 100 * 1024 * 1024; if ($this->calculateEstimatedFootprint($from, $to) > $memoryLimit) { return new MemoryEfficientLongestCommonSubsequenceCalculator; } return new TimeEfficientLongestCommonSubsequenceCalculator; } private function calculateEstimatedFootprint(array $from, array $to) { $itemSize = PHP_INT_SIZE === 4 ? 76 : 144; return $itemSize * \min(\count($from), \count($to)) ** 2; } private function detectUnmatchedLineEndings(array $diff) { $newLineBreaks = ['' => true]; $oldLineBreaks = ['' => true]; foreach ($diff as $entry) { if (0 === $entry[1]) { $ln = $this->getLinebreak($entry[0]); $oldLineBreaks[$ln] = true; $newLineBreaks[$ln] = true; } elseif (1 === $entry[1]) { $newLineBreaks[$this->getLinebreak($entry[0])] = true; } elseif (2 === $entry[1]) { $oldLineBreaks[$this->getLinebreak($entry[0])] = true; } } if (['' => true] === $newLineBreaks || ['' => true] === $oldLineBreaks) { return false; } foreach ($newLineBreaks as $break => $set) { if (!isset($oldLineBreaks[$break])) { return true; } } foreach ($oldLineBreaks as $break => $set) { if (!isset($newLineBreaks[$break])) { return true; } } return false; } private function getLinebreak($line) { if (!\is_string($line)) { return ''; } $lc = \substr($line, -1); if ("\r" === $lc) { return "\r"; } if ("\n" !== $lc) { return ''; } if ("\r\n" === \substr($line, -2)) { return "\r\n"; } return "\n"; } private static function getArrayDiffParted(array &$from, array &$to) { $start = []; $end = []; \reset($to); foreach ($from as $k => $v) { $toK = \key($to); if ($toK === $k && $v === $to[$k]) { $start[$k] = $v; unset($from[$k], $to[$k]); } else { break; } } \end($from); \end($to); do { $fromK = \key($from); $toK = \key($to); if (null === $fromK || null === $toK || \current($from) !== \current($to)) { break; } \prev($from); \prev($to); $end = [$fromK => $from[$fromK]] + $end; unset($from[$fromK], $to[$toK]); } while (true); return [$from, $to, $start, $end]; } } 0 && $j > 0) { if ($from[$i - 1] === $to[$j - 1]) { $common[] = $from[$i - 1]; --$i; --$j; } else { $o = ($j * $width) + $i; if ($matrix[$o - $width] > $matrix[$o - 1]) { --$j; } else { --$i; } } } return \array_reverse($common); } } \\S+))', $lines[$i], $fromMatch) && \preg_match('(^\\+\\+\\+\\s+(?P\\S+))', $lines[$i + 1], $toMatch)) { if ($diff !== null) { $this->parseFileDiff($diff, $collected); $diffs[] = $diff; $collected = []; } $diff = new Diff($fromMatch['file'], $toMatch['file']); ++$i; } else { if (\preg_match('/^(?:diff --git |index [\da-f\.]+|[+-]{3} [ab])/', $lines[$i])) { continue; } $collected[] = $lines[$i]; } } if ($diff !== null && \count($collected)) { $this->parseFileDiff($diff, $collected); $diffs[] = $diff; } return $diffs; } private function parseFileDiff(Diff $diff, array $lines) { $chunks = []; $chunk = null; foreach ($lines as $line) { if (\preg_match('/^@@\s+-(?P\d+)(?:,\s*(?P\d+))?\s+\+(?P\d+)(?:,\s*(?P\d+))?\s+@@/', $line, $match)) { $chunk = new Chunk( (int) $match['start'], isset($match['startrange']) ? \max(1, (int) $match['startrange']) : 1, (int) $match['end'], isset($match['endrange']) ? \max(1, (int) $match['endrange']) : 1 ); $chunks[] = $chunk; $diffLines = []; continue; } if (\preg_match('/^(?P[+ -])?(?P.*)/', $line, $match)) { $type = Line::UNCHANGED; if ($match['type'] === '+') { $type = Line::ADDED; } elseif ($match['type'] === '-') { $type = Line::REMOVED; } $diffLines[] = new Line($type, $match['line']); if (null !== $chunk) { $chunk->setLines($diffLines); } } } $diff->setChunks($chunks); } } start = $start; $this->startRange = $startRange; $this->end = $end; $this->endRange = $endRange; $this->lines = $lines; } public function getStart() { return $this->start; } public function getStartRange() { return $this->startRange; } public function getEnd() { return $this->end; } public function getEndRange() { return $this->endRange; } public function getLines() { return $this->lines; } public function setLines(array $lines) { $this->lines = $lines; } } type = $type; $this->content = $content; } public function getContent() { return $this->content; } public function getType() { return $this->type; } } length($fromStart, $to); $llE = $this->length(\array_reverse($fromEnd), \array_reverse($to)); $jMax = 0; $max = 0; for ($j = 0; $j <= $cTo; $j++) { $m = $llB[$j] + $llE[$cTo - $j]; if ($m >= $max) { $max = $m; $jMax = $j; } } $toStart = \array_slice($to, 0, $jMax); $toEnd = \array_slice($to, $jMax); return \array_merge( $this->calculate($fromStart, $toStart), $this->calculate($fromEnd, $toEnd) ); } private function length(array $from, array $to) { $current = \array_fill(0, \count($to) + 1, 0); $cFrom = \count($from); $cTo = \count($to); for ($i = 0; $i < $cFrom; $i++) { $prev = $current; for ($j = 0; $j < $cTo; $j++) { if ($from[$i] === $to[$j]) { $current[$j + 1] = $prev[$j] + 1; } else { $current[$j + 1] = \max($current[$j], $prev[$j + 1]); } } } return $current; } } from = $from; $this->to = $to; $this->chunks = $chunks; } public function getFrom() { return $this->from; } public function getTo() { return $this->to; } public function getChunks() { return $this->chunks; } public function setChunks(array $chunks) { $this->chunks = $chunks; } } 0 && $j > 0) { if ($from[$i - 1] === $to[$j - 1]) { $common[] = $from[$i - 1]; --$i; --$j; } else { $o = ($j * $width) + $i; if ($matrix[$o - $width] > $matrix[$o - 1]) { --$j; } else { --$i; } } } return \array_reverse($common); } } length($fromStart, $to); $llE = $this->length(\array_reverse($fromEnd), \array_reverse($to)); $jMax = 0; $max = 0; for ($j = 0; $j <= $cTo; $j++) { $m = $llB[$j] + $llE[$cTo - $j]; if ($m >= $max) { $max = $m; $jMax = $j; } } $toStart = \array_slice($to, 0, $jMax); $toEnd = \array_slice($to, $jMax); return \array_merge( $this->calculate($fromStart, $toStart), $this->calculate($fromEnd, $toEnd) ); } private function length(array $from, array $to) { $current = \array_fill(0, \count($to) + 1, 0); $cFrom = \count($from); $cTo = \count($to); for ($i = 0; $i < $cFrom; $i++) { $prev = $current; for ($j = 0; $j < $cTo; $j++) { if ($from[$i] === $to[$j]) { $current[$j + 1] = $prev[$j] + 1; } else { $current[$j + 1] = \max($current[$j], $prev[$j + 1]); } } } return $current; } } header = $header; $this->showNonDiffLines = $showNonDiffLines; } public function diff($from, $to, LongestCommonSubsequence $lcs = null) { $from = $this->validateDiffInput($from); $to = $this->validateDiffInput($to); $diff = $this->diffToArray($from, $to, $lcs); $old = $this->checkIfDiffInOld($diff); $start = isset($old[0]) ? $old[0] : 0; $end = \count($diff); if ($tmp = \array_search($end, $old)) { $end = $tmp; } return $this->getBuffer($diff, $old, $start, $end); } private function validateDiffInput($input) { if (!\is_array($input) && !\is_string($input)) { return (string) $input; } return $input; } private function checkIfDiffInOld(array $diff) { $inOld = false; $i = 0; $old = array(); foreach ($diff as $line) { if ($line[1] === 0 ) { if ($inOld === false) { $inOld = $i; } } elseif ($inOld !== false) { if (($i - $inOld) > 5) { $old[$inOld] = $i - 1; } $inOld = false; } ++$i; } return $old; } private function getBuffer(array $diff, array $old, $start, $end) { $buffer = $this->header; if (!isset($old[$start])) { $buffer = $this->getDiffBufferElementNew($diff, $buffer, $start); ++$start; } for ($i = $start; $i < $end; $i++) { if (isset($old[$i])) { $i = $old[$i]; $buffer = $this->getDiffBufferElementNew($diff, $buffer, $i); } else { $buffer = $this->getDiffBufferElement($diff, $buffer, $i); } } return $buffer; } private function getDiffBufferElement(array $diff, $buffer, $diffIndex) { if ($diff[$diffIndex][1] === 1 ) { $buffer .= '+' . $diff[$diffIndex][0] . "\n"; } elseif ($diff[$diffIndex][1] === 2 ) { $buffer .= '-' . $diff[$diffIndex][0] . "\n"; } elseif ($this->showNonDiffLines === true) { $buffer .= ' ' . $diff[$diffIndex][0] . "\n"; } return $buffer; } private function getDiffBufferElementNew(array $diff, $buffer, $diffIndex) { if ($this->showNonDiffLines === true) { $buffer .= "@@ @@\n"; } return $this->getDiffBufferElement($diff, $buffer, $diffIndex); } public function diffToArray($from, $to, LongestCommonSubsequence $lcs = null) { if (\is_string($from)) { $fromMatches = $this->getNewLineMatches($from); $from = $this->splitStringByLines($from); } elseif (\is_array($from)) { $fromMatches = array(); } else { throw new \InvalidArgumentException('"from" must be an array or string.'); } if (\is_string($to)) { $toMatches = $this->getNewLineMatches($to); $to = $this->splitStringByLines($to); } elseif (\is_array($to)) { $toMatches = array(); } else { throw new \InvalidArgumentException('"to" must be an array or string.'); } list($from, $to, $start, $end) = self::getArrayDiffParted($from, $to); if ($lcs === null) { $lcs = $this->selectLcsImplementation($from, $to); } $common = $lcs->calculate(\array_values($from), \array_values($to)); $diff = array(); if ($this->detectUnmatchedLineEndings($fromMatches, $toMatches)) { $diff[] = array( '#Warnings contain different line endings!', 0 ); } foreach ($start as $token) { $diff[] = array($token, 0 ); } \reset($from); \reset($to); foreach ($common as $token) { while (($fromToken = \reset($from)) !== $token) { $diff[] = array(\array_shift($from), 2 ); } while (($toToken = \reset($to)) !== $token) { $diff[] = array(\array_shift($to), 1 ); } $diff[] = array($token, 0 ); \array_shift($from); \array_shift($to); } while (($token = \array_shift($from)) !== null) { $diff[] = array($token, 2 ); } while (($token = \array_shift($to)) !== null) { $diff[] = array($token, 1 ); } foreach ($end as $token) { $diff[] = array($token, 0 ); } return $diff; } private function getNewLineMatches($string) { \preg_match_all('(\r\n|\r|\n)', $string, $stringMatches); return $stringMatches; } private function splitStringByLines($input) { return \preg_split('(\r\n|\r|\n)', $input); } private function selectLcsImplementation(array $from, array $to) { $memoryLimit = 100 * 1024 * 1024; if ($this->calculateEstimatedFootprint($from, $to) > $memoryLimit) { return new MemoryEfficientImplementation; } return new TimeEfficientImplementation; } private function calculateEstimatedFootprint(array $from, array $to) { $itemSize = PHP_INT_SIZE === 4 ? 76 : 144; return $itemSize * \pow(\min(\count($from), \count($to)), 2); } private function detectUnmatchedLineEndings(array $fromMatches, array $toMatches) { return isset($fromMatches[0], $toMatches[0]) && \count($fromMatches[0]) === \count($toMatches[0]) && $fromMatches[0] !== $toMatches[0]; } private static function getArrayDiffParted(array &$from, array &$to) { $start = array(); $end = array(); \reset($to); foreach ($from as $k => $v) { $toK = \key($to); if ($toK === $k && $v === $to[$k]) { $start[$k] = $v; unset($from[$k], $to[$k]); } else { break; } } \end($from); \end($to); do { $fromK = \key($from); $toK = \key($to); if (null === $fromK || null === $toK || \current($from) !== \current($to)) { break; } \prev($from); \prev($to); $end = array($fromK => $from[$fromK]) + $end; unset($from[$fromK], $to[$toK]); } while (true); return array($from, $to, $start, $end); } } \\S+))', $lines[$i], $fromMatch) && \preg_match('(^\\+\\+\\+\\s+(?P\\S+))', $lines[$i + 1], $toMatch)) { if ($diff !== null) { $this->parseFileDiff($diff, $collected); $diffs[] = $diff; $collected = array(); } $diff = new Diff($fromMatch['file'], $toMatch['file']); ++$i; } else { if (\preg_match('/^(?:diff --git |index [\da-f\.]+|[+-]{3} [ab])/', $lines[$i])) { continue; } $collected[] = $lines[$i]; } } if ($diff !== null && \count($collected)) { $this->parseFileDiff($diff, $collected); $diffs[] = $diff; } return $diffs; } private function parseFileDiff(Diff $diff, array $lines) { $chunks = array(); $chunk = null; foreach ($lines as $line) { if (\preg_match('/^@@\s+-(?P\d+)(?:,\s*(?P\d+))?\s+\+(?P\d+)(?:,\s*(?P\d+))?\s+@@/', $line, $match)) { $chunk = new Chunk( $match['start'], isset($match['startrange']) ? \max(1, $match['startrange']) : 1, $match['end'], isset($match['endrange']) ? \max(1, $match['endrange']) : 1 ); $chunks[] = $chunk; $diffLines = array(); continue; } if (\preg_match('/^(?P[+ -])?(?P.*)/', $line, $match)) { $type = Line::UNCHANGED; if ($match['type'] === '+') { $type = Line::ADDED; } elseif ($match['type'] === '-') { $type = Line::REMOVED; } $diffLines[] = new Line($type, $match['line']); if (null !== $chunk) { $chunk->setLines($diffLines); } } } $diff->setChunks($chunks); } } start = (int) $start; $this->startRange = (int) $startRange; $this->end = (int) $end; $this->endRange = (int) $endRange; $this->lines = $lines; } public function getStart() { return $this->start; } public function getStartRange() { return $this->startRange; } public function getEnd() { return $this->end; } public function getEndRange() { return $this->endRange; } public function getLines() { return $this->lines; } public function setLines(array $lines) { $this->lines = $lines; } } type = $type; $this->content = $content; } public function getContent() { return $this->content; } public function getType() { return $this->type; } } error = $error; } public function check() { if (null !== $this->error) { throw new LintingException( sprintf('PHP Parse error: %s on line %d.', $this->error->getMessage(), $this->error->getLine()), $this->error->getCode(), $this->error ); } } } find(false); if (false === $executable) { throw new UnavailableLinterException('Cannot find PHP executable.'); } if ('phpdbg' === \PHP_SAPI) { if (false === strpos($executable, 'phpdbg')) { throw new UnavailableLinterException('Automatically found PHP executable is non-standard phpdbg. Could not find proper PHP executable.'); } $executable = str_replace('phpdbg', 'php', $executable); if (!is_executable($executable)) { throw new UnavailableLinterException('Automatically found PHP executable is phpdbg. Could not find proper PHP executable.'); } } } $this->processBuilder = new ProcessLinterProcessBuilder($executable); $this->fileRemoval = new FileRemoval(); } public function __destruct() { if (null !== $this->temporaryFile) { $this->fileRemoval->delete($this->temporaryFile); } } public function isAsync() { return true; } public function lintFile($path) { return new ProcessLintingResult($this->createProcessForFile($path)); } public function lintSource($source) { return new ProcessLintingResult($this->createProcessForSource($source)); } private function createProcessForFile($path) { if (!is_file($path)) { return $this->createProcessForSource(FileReader::createSingleton()->read($path)); } $process = $this->processBuilder->build($path); $process->setTimeout(10); $process->start(); return $process; } private function createProcessForSource($source) { if (null === $this->temporaryFile) { $this->temporaryFile = tempnam('.', 'cs_fixer_tmp_'); $this->fileRemoval->observe($this->temporaryFile); } if (false === @file_put_contents($this->temporaryFile, $source)) { throw new IOException(sprintf('Failed to write file "%s".', $this->temporaryFile), 0, null, $this->temporaryFile); } return $this->createProcessForFile($this->temporaryFile); } } sublinter = new TokenizerLinter(); } catch (UnavailableLinterException $e) { $this->sublinter = new ProcessLinter($executable); } } public function isAsync() { return $this->sublinter->isAsync(); } public function lintFile($path) { return $this->sublinter->lintFile($path); } public function lintSource($source) { return $this->sublinter->lintSource($source); } } executable = $executable; } public function build($path) { return new Process([ $this->executable, '-l', $path, ]); } } process = $process; } public function check() { if (!$this->isSuccessful()) { throw new LintingException($this->process->getErrorOutput() ?: $this->process->getOutput(), $this->process->getExitCode()); } } private function isSuccessful() { if (null === $this->isSuccessful) { $this->process->wait(); $this->isSuccessful = $this->process->isSuccessful(); } return $this->isSuccessful; } } lintSource(FileReader::createSingleton()->read($path)); } public function lintSource($source) { try { $codeHash = CodeHasher::calculateCodeHash($source); Tokens::clearCache($codeHash); Tokens::fromCode($source); return new TokenizerLintingResult(); } catch (\ParseError $e) { return new TokenizerLintingResult($e); } } } sublinter = $linter; } public function isAsync() { return $this->sublinter->isAsync(); } public function lintFile($path) { $checksum = crc32(file_get_contents($path)); if (!isset($this->cache[$checksum])) { $this->cache[$checksum] = $this->sublinter->lintFile($path); } return $this->cache[$checksum]; } public function lintSource($source) { $checksum = crc32($source); if (!isset($this->cache[$checksum])) { $this->cache[$checksum] = $this->sublinter->lintSource($source); } return $this->cache[$checksum]; } } clean(); } public function observe($path) { $this->files[$path] = true; } public function delete($path) { if (isset($this->files[$path])) { unset($this->files[$path]); } $this->unlink($path); } public function clean() { foreach ($this->files as $file => $value) { $this->unlink($file); } $this->files = []; } private function unlink($path) { @unlink($path); } } toolInfo = new ToolInfo(); $this->add(new DescribeCommand()); $this->add(new FixCommand($this->toolInfo)); $this->add(new ReadmeCommand()); $this->add(new SelfUpdateCommand( new NewVersionChecker(new GithubClient()), $this->toolInfo, new PharChecker() )); } public static function getMajorVersion() { return (int) explode('.', self::VERSION)[0]; } public function doRun(InputInterface $input, OutputInterface $output) { $stdErr = $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : ($input->hasParameterOption('--format', true) && 'txt' !== $input->getParameterOption('--format', null, true) ? null : $output) ; if (null !== $stdErr) { $warningsDetector = new WarningsDetector($this->toolInfo); $warningsDetector->detectOldVendor(); $warningsDetector->detectOldMajor(); foreach ($warningsDetector->getWarnings() as $warning) { $stdErr->writeln(sprintf($stdErr->isDecorated() ? '%s' : '%s', $warning)); } } return parent::doRun($input, $output); } public function getLongVersion() { $version = sprintf( '%s %s by Fabien Potencier and Dariusz Ruminski', parent::getLongVersion(), self::VERSION_CODENAME ); $commit = 'd69fb62e3cffcc73b92154c84ce0e8f9fc5d0c41'; if ('@'.'git-commit@' !== $commit) { $version .= ' ('.substr($commit, 0, 7).')'; } return $version; } protected function getDefaultCommands() { return [new HelpCommand(), new ListCommand()]; } } [ 'header' => 'User-Agent: FriendsOfPHP/PHP-CS-Fixer', ], ]) ); if (false === $result) { throw new \RuntimeException(sprintf('Failed to load tags at "%s".', $url)); } $result = json_decode($result, true); if (JSON_ERROR_NONE !== json_last_error()) { throw new \RuntimeException(sprintf( 'Failed to read response from "%s" as JSON: %s.', $url, json_last_error_msg() )); } return $result; } } githubClient = $githubClient; $this->versionParser = new VersionParser(); } public function getLatestVersion() { $this->retrieveAvailableVersions(); return $this->availableVersions[0]; } public function getLatestVersionOfMajor($majorVersion) { $this->retrieveAvailableVersions(); $semverConstraint = '^'.$majorVersion; foreach ($this->availableVersions as $availableVersion) { if (Semver::satisfies($availableVersion, $semverConstraint)) { return $availableVersion; } } return null; } public function compareVersions($versionA, $versionB) { $versionA = $this->versionParser->normalize($versionA); $versionB = $this->versionParser->normalize($versionB); if (Comparator::lessThan($versionA, $versionB)) { return -1; } if (Comparator::greaterThan($versionA, $versionB)) { return 1; } return 0; } private function retrieveAvailableVersions() { if (null !== $this->availableVersions) { return; } foreach ($this->githubClient->getTags() as $tag) { $version = $tag['name']; try { $this->versionParser->normalize($version); if ('stable' === VersionParser::parseStability($version)) { $this->availableVersions[] = $version; } } catch (\UnexpectedValueException $exception) { } } $this->availableVersions = Semver::rsort($this->availableVersions); } } output = $output; $this->isDecorated = $output->isDecorated(); } public function listErrors($process, array $errors) { $this->output->writeln(['', sprintf( 'Files that were not fixed due to errors reported during %s:', $process )]); $showDetails = $this->output->getVerbosity() >= OutputInterface::VERBOSITY_VERY_VERBOSE; $showTrace = $this->output->getVerbosity() >= OutputInterface::VERBOSITY_DEBUG; foreach ($errors as $i => $error) { $this->output->writeln(sprintf('%4d) %s', $i + 1, $error->getFilePath())); $e = $error->getSource(); if (!$showDetails || null === $e) { continue; } $class = sprintf('[%s]', \get_class($e)); $message = $e->getMessage(); $code = $e->getCode(); if (0 !== $code) { $message .= " ({$code})"; } $length = max(\strlen($class), \strlen($message)); $lines = [ '', $class, $message, '', ]; $this->output->writeln(''); foreach ($lines as $line) { if (\strlen($line) < $length) { $line .= str_repeat(' ', $length - \strlen($line)); } $this->output->writeln(sprintf(' %s ', $this->prepareOutput($line))); } if ($showTrace && !$e instanceof LintingException) { $this->output->writeln(''); $stackTrace = $e->getTrace(); foreach ($stackTrace as $trace) { if (isset($trace['class'], $trace['function']) && \Symfony\Component\Console\Command\Command::class === $trace['class'] && 'run' === $trace['function']) { $this->output->writeln(' [ ... ]'); break; } $this->outputTrace($trace); } } if (Error::TYPE_LINT === $error->getType() && 0 < \count($error->getAppliedFixers())) { $this->output->writeln(''); $this->output->writeln(sprintf(' Applied fixers: %s', implode(', ', $error->getAppliedFixers()))); $diff = $error->getDiff(); if (!empty($diff)) { $diffFormatter = new DiffConsoleFormatter( $this->isDecorated, sprintf( ' ---------- begin diff ----------%s%%s%s ----------- end diff -----------', PHP_EOL, PHP_EOL ) ); $this->output->writeln($diffFormatter->format($diff)); } } } } private function outputTrace(array $trace) { if (isset($trace['class'], $trace['type'], $trace['function'])) { $this->output->writeln(sprintf( ' %s%s%s()', $this->prepareOutput($trace['class']), $this->prepareOutput($trace['type']), $this->prepareOutput($trace['function']) )); } elseif (isset($trace['function'])) { $this->output->writeln(sprintf(' %s()', $this->prepareOutput($trace['function']))); } if (isset($trace['file'])) { $this->output->writeln(sprintf(' in %s at line %d', $this->prepareOutput($trace['file']), $trace['line'])); } } private function prepareOutput($string) { return $this->isDecorated ? OutputFormatter::escape($string) : $string ; } } ['symbol' => '?', 'format' => '%s', 'description' => 'unknown'], FixerFileProcessedEvent::STATUS_INVALID => ['symbol' => 'I', 'format' => '%s', 'description' => 'invalid file syntax (file ignored)'], FixerFileProcessedEvent::STATUS_SKIPPED => ['symbol' => 'S', 'format' => '%s', 'description' => 'skipped (cached or empty file)'], FixerFileProcessedEvent::STATUS_NO_CHANGES => ['symbol' => '.', 'format' => '%s', 'description' => 'no changes'], FixerFileProcessedEvent::STATUS_FIXED => ['symbol' => 'F', 'format' => '%s', 'description' => 'fixed'], FixerFileProcessedEvent::STATUS_EXCEPTION => ['symbol' => 'E', 'format' => '%s', 'description' => 'error'], FixerFileProcessedEvent::STATUS_LINT => ['symbol' => 'E', 'format' => '%s', 'description' => 'error'], ]; private $eventDispatcher; private $output; private $files; private $processedFiles = 0; private $symbolsPerLine; public function __construct(OutputInterface $output, EventDispatcherInterface $dispatcher, $width, $nbFiles) { $this->output = $output; $this->eventDispatcher = $dispatcher; $this->eventDispatcher->addListener(FixerFileProcessedEvent::NAME, [$this, 'onFixerFileProcessed']); $this->symbolsPerLine = $width; if (null !== $nbFiles) { $this->files = $nbFiles; $this->symbolsPerLine = max(1, ($width ?: 80) - \strlen((string) $this->files) * 2 - 11); } } public function __destruct() { $this->eventDispatcher->removeListener(FixerFileProcessedEvent::NAME, [$this, 'onFixerFileProcessed']); } public function onFixerFileProcessed(FixerFileProcessedEvent $event) { if ( null === $this->files && null !== $this->symbolsPerLine && 0 === $this->processedFiles % $this->symbolsPerLine && 0 !== $this->processedFiles ) { $this->output->writeln(''); } $status = self::$eventStatusMap[$event->getStatus()]; $this->output->write($this->output->isDecorated() ? sprintf($status['format'], $status['symbol']) : $status['symbol']); ++$this->processedFiles; if (null !== $this->files) { $symbolsOnCurrentLine = $this->processedFiles % $this->symbolsPerLine; $isLast = $this->processedFiles === $this->files; if (0 === $symbolsOnCurrentLine || $isLast) { $this->output->write(sprintf( '%s %'.\strlen((string) $this->files).'d / %d (%3d%%)', $isLast && 0 !== $symbolsOnCurrentLine ? str_repeat(' ', $this->symbolsPerLine - $symbolsOnCurrentLine) : '', $this->processedFiles, $this->files, round($this->processedFiles / $this->files * 100) )); if (!$isLast) { $this->output->writeln(''); } } } } public function printLegend() { $symbols = []; foreach (self::$eventStatusMap as $status) { $symbol = $status['symbol']; if ('' === $symbol || isset($symbols[$symbol])) { continue; } $symbols[$symbol] = sprintf('%s-%s', $this->output->isDecorated() ? sprintf($status['format'], $symbol) : $symbol, $status['description']); } $this->output->write(sprintf("\nLegend: %s\n", implode(', ', $symbols))); } } null, 'cache-file' => null, 'config' => null, 'diff' => null, 'diff-format' => null, 'dry-run' => null, 'format' => null, 'path' => [], 'path-mode' => self::PATH_MODE_OVERRIDE, 'rules' => null, 'show-progress' => null, 'stop-on-violation' => null, 'using-cache' => null, 'verbosity' => null, ]; private $cacheFile; private $cacheManager; private $differ; private $directory; private $finder; private $format; private $linter; private $path; private $progress; private $ruleSet; private $usingCache; private $fixerFactory; public function __construct( ConfigInterface $config, array $options, $cwd, ToolInfoInterface $toolInfo ) { $this->cwd = $cwd; $this->defaultConfig = $config; $this->toolInfo = $toolInfo; foreach ($options as $name => $value) { $this->setOption($name, $value); } } public function getCacheFile() { if (!$this->getUsingCache()) { return null; } if (null === $this->cacheFile) { if (null === $this->options['cache-file']) { $this->cacheFile = $this->getConfig()->getCacheFile(); } else { $this->cacheFile = $this->options['cache-file']; } } return $this->cacheFile; } public function getCacheManager() { if (null === $this->cacheManager) { if ($this->getUsingCache() && ($this->toolInfo->isInstalledAsPhar() || $this->toolInfo->isInstalledByComposer())) { $this->cacheManager = new FileCacheManager( new FileHandler($this->getCacheFile()), new Signature( PHP_VERSION, $this->toolInfo->getVersion(), $this->getConfig()->getIndent(), $this->getConfig()->getLineEnding(), $this->getRules() ), $this->isDryRun(), $this->getDirectory() ); } else { $this->cacheManager = new NullCacheManager(); } } return $this->cacheManager; } public function getConfig() { if (null === $this->config) { foreach ($this->computeConfigFiles() as $configFile) { if (!file_exists($configFile)) { continue; } $config = self::separatedContextLessInclude($configFile); if (!$config instanceof ConfigInterface) { throw new InvalidConfigurationException(sprintf('The config file: "%s" does not return a "PhpCsFixer\ConfigInterface" instance. Got: "%s".', $configFile, \is_object($config) ? \get_class($config) : \gettype($config))); } $this->config = $config; $this->configFile = $configFile; break; } if (null === $this->config) { $this->config = $this->defaultConfig; } } return $this->config; } public function getConfigFile() { if (null === $this->configFile) { $this->getConfig(); } return $this->configFile; } public function getDiffer() { if (null === $this->differ) { $mapper = [ 'null' => static function () { return new NullDiffer(); }, 'sbd' => static function () { return new SebastianBergmannDiffer(); }, 'udiff' => static function () { return new UnifiedDiffer(); }, ]; if ($this->options['diff-format']) { $option = $this->options['diff-format']; if (!isset($mapper[$option])) { throw new InvalidConfigurationException(sprintf( '"diff-format" must be any of "%s", got "%s".', implode('", "', array_keys($mapper)), $option )); } } else { $default = 'sbd'; if (getenv('PHP_CS_FIXER_FUTURE_MODE')) { $default = 'udiff'; } $option = $this->options['diff'] ? $default : 'null'; } $this->differ = $mapper[$option](); } return $this->differ; } public function getDirectory() { if (null === $this->directory) { $path = $this->getCacheFile(); if (null === $path) { $absolutePath = $this->cwd; } else { $filesystem = new Filesystem(); $absolutePath = $filesystem->isAbsolutePath($path) ? $path : $this->cwd.\DIRECTORY_SEPARATOR.$path; } $this->directory = new Directory(\dirname($absolutePath)); } return $this->directory; } public function getFixers() { if (null === $this->fixers) { $this->fixers = $this->createFixerFactory() ->useRuleSet($this->getRuleSet()) ->setWhitespacesConfig(new WhitespacesFixerConfig($this->config->getIndent(), $this->config->getLineEnding())) ->getFixers() ; if (false === $this->getRiskyAllowed()) { $riskyFixers = array_map( static function (FixerInterface $fixer) { return $fixer->getName(); }, array_filter( $this->fixers, static function (FixerInterface $fixer) { return $fixer->isRisky(); } ) ); if (\count($riskyFixers)) { throw new InvalidConfigurationException(sprintf('The rules contain risky fixers (%s), but they are not allowed to run. Perhaps you forget to use --allow-risky=yes option?', implode(', ', $riskyFixers))); } } } return $this->fixers; } public function getLinter() { if (null === $this->linter) { $this->linter = new Linter($this->getConfig()->getPhpExecutable()); } return $this->linter; } public function getPath() { if (null === $this->path) { $filesystem = new Filesystem(); $cwd = $this->cwd; if (1 === \count($this->options['path']) && '-' === $this->options['path'][0]) { $this->path = $this->options['path']; } else { $this->path = array_map( static function ($path) use ($cwd, $filesystem) { $absolutePath = $filesystem->isAbsolutePath($path) ? $path : $cwd.\DIRECTORY_SEPARATOR.$path; if (!file_exists($absolutePath)) { throw new InvalidConfigurationException(sprintf( 'The path "%s" is not readable.', $path )); } return $absolutePath; }, $this->options['path'] ); } } return $this->path; } public function getProgress() { if (null === $this->progress) { if (OutputInterface::VERBOSITY_VERBOSE <= $this->options['verbosity'] && 'txt' === $this->getFormat()) { $progressType = $this->options['show-progress']; $progressTypes = ['none', 'run-in', 'estimating', 'estimating-max', 'dots']; if (null === $progressType) { $default = 'run-in'; if (getenv('PHP_CS_FIXER_FUTURE_MODE')) { $default = 'dots'; } $progressType = $this->getConfig()->getHideProgress() ? 'none' : $default; } elseif (!\in_array($progressType, $progressTypes, true)) { throw new InvalidConfigurationException(sprintf( 'The progress type "%s" is not defined, supported are "%s".', $progressType, implode('", "', $progressTypes) )); } elseif (\in_array($progressType, ['estimating', 'estimating-max', 'run-in'], true)) { $message = 'Passing `estimating`, `estimating-max` or `run-in` is deprecated and will not be supported in 3.0, use `none` or `dots` instead.'; if (getenv('PHP_CS_FIXER_FUTURE_MODE')) { throw new \InvalidArgumentException("{$message} This check was performed as `PHP_CS_FIXER_FUTURE_MODE` env var is set."); } @trigger_error($message, E_USER_DEPRECATED); } $this->progress = $progressType; } else { $this->progress = 'none'; } } return $this->progress; } public function getReporter() { if (null === $this->reporter) { $reporterFactory = ReporterFactory::create(); $reporterFactory->registerBuiltInReporters(); $format = $this->getFormat(); try { $this->reporter = $reporterFactory->getReporter($format); } catch (\UnexpectedValueException $e) { $formats = $reporterFactory->getFormats(); sort($formats); throw new InvalidConfigurationException(sprintf('The format "%s" is not defined, supported are "%s".', $format, implode('", "', $formats))); } } return $this->reporter; } public function getRiskyAllowed() { if (null === $this->allowRisky) { if (null === $this->options['allow-risky']) { $this->allowRisky = $this->getConfig()->getRiskyAllowed(); } else { $this->allowRisky = $this->resolveOptionBooleanValue('allow-risky'); } } return $this->allowRisky; } public function getRules() { return $this->getRuleSet()->getRules(); } public function getUsingCache() { if (null === $this->usingCache) { if (null === $this->options['using-cache']) { $this->usingCache = $this->getConfig()->getUsingCache(); } else { $this->usingCache = $this->resolveOptionBooleanValue('using-cache'); } } return $this->usingCache; } public function getFinder() { if (null === $this->finder) { $this->finder = $this->resolveFinder(); } return $this->finder; } public function isDryRun() { if (null === $this->isDryRun) { if ($this->isStdIn()) { $this->isDryRun = true; } else { $this->isDryRun = $this->options['dry-run']; } } return $this->isDryRun; } public function shouldStopOnViolation() { return $this->options['stop-on-violation']; } public function configFinderIsOverridden() { if (null === $this->configFinderIsOverridden) { $this->resolveFinder(); } return $this->configFinderIsOverridden; } private function computeConfigFiles() { $configFile = $this->options['config']; if (null !== $configFile) { if (false === file_exists($configFile) || false === is_readable($configFile)) { throw new InvalidConfigurationException(sprintf('Cannot read config file "%s".', $configFile)); } return [$configFile]; } $path = $this->getPath(); if ($this->isStdIn() || 0 === \count($path)) { $configDir = $this->cwd; } elseif (1 < \count($path)) { throw new InvalidConfigurationException('For multiple paths config parameter is required.'); } elseif (is_file($path[0]) && $dirName = pathinfo($path[0], PATHINFO_DIRNAME)) { $configDir = $dirName; } else { $configDir = $path[0]; } $candidates = [ $configDir.\DIRECTORY_SEPARATOR.'.php_cs', $configDir.\DIRECTORY_SEPARATOR.'.php_cs.dist', ]; if ($configDir !== $this->cwd) { $candidates[] = $this->cwd.\DIRECTORY_SEPARATOR.'.php_cs'; $candidates[] = $this->cwd.\DIRECTORY_SEPARATOR.'.php_cs.dist'; } return $candidates; } private function createFixerFactory() { if (null === $this->fixerFactory) { $fixerFactory = new FixerFactory(); $fixerFactory->registerBuiltInFixers(); $fixerFactory->registerCustomFixers($this->getConfig()->getCustomFixers()); $this->fixerFactory = $fixerFactory; } return $this->fixerFactory; } private function getFormat() { if (null === $this->format) { $this->format = null === $this->options['format'] ? $this->getConfig()->getFormat() : $this->options['format']; } return $this->format; } private function getRuleSet() { if (null === $this->ruleSet) { $rules = $this->parseRules(); $this->validateRules($rules); $this->ruleSet = new RuleSet($rules); } return $this->ruleSet; } private function isStdIn() { if (null === $this->isStdIn) { $this->isStdIn = 1 === \count($this->options['path']) && '-' === $this->options['path'][0]; } return $this->isStdIn; } private function iterableToTraversable($iterable) { return \is_array($iterable) ? new \ArrayIterator($iterable) : $iterable; } private function parseRules() { if (null === $this->options['rules']) { return $this->getConfig()->getRules(); } $rules = trim($this->options['rules']); if ('' === $rules) { throw new InvalidConfigurationException('Empty rules value is not allowed.'); } if ('{' === $rules[0]) { $rules = json_decode($rules, true); if (JSON_ERROR_NONE !== json_last_error()) { throw new InvalidConfigurationException(sprintf('Invalid JSON rules input: "%s".', json_last_error_msg())); } return $rules; } $rules = []; foreach (explode(',', $this->options['rules']) as $rule) { $rule = trim($rule); if ('' === $rule) { throw new InvalidConfigurationException('Empty rule name is not allowed.'); } if ('-' === $rule[0]) { $rules[substr($rule, 1)] = false; } else { $rules[$rule] = true; } } return $rules; } private function validateRules(array $rules) { $ruleSet = []; foreach ($rules as $key => $value) { if (\is_int($key)) { throw new InvalidConfigurationException(sprintf('Missing value for "%s" rule/set.', $value)); } $ruleSet[$key] = true; } $ruleSet = new RuleSet($ruleSet); $configuredFixers = array_keys($ruleSet->getRules()); $fixers = $this->createFixerFactory()->getFixers(); $availableFixers = array_map(static function (FixerInterface $fixer) { return $fixer->getName(); }, $fixers); $unknownFixers = array_diff( $configuredFixers, $availableFixers ); if (\count($unknownFixers)) { $matcher = new WordMatcher($availableFixers); $message = 'The rules contain unknown fixers: '; foreach ($unknownFixers as $unknownFixer) { $alternative = $matcher->match($unknownFixer); $message .= sprintf( '"%s"%s, ', $unknownFixer, null === $alternative ? '' : ' (did you mean "'.$alternative.'"?)' ); } throw new InvalidConfigurationException(substr($message, 0, -2).'.'); } foreach ($fixers as $fixer) { $fixerName = $fixer->getName(); if (isset($rules[$fixerName]) && $fixer instanceof DeprecatedFixerInterface) { $successors = $fixer->getSuccessorsNames(); $messageEnd = [] === $successors ? sprintf(' and will be removed in version %d.0.', Application::getMajorVersion()) : sprintf('. Use %s instead.', str_replace('`', '"', Utils::naturalLanguageJoinWithBackticks($successors))); $message = "Rule \"{$fixerName}\" is deprecated{$messageEnd}"; if (getenv('PHP_CS_FIXER_FUTURE_MODE')) { throw new \RuntimeException("{$message} This check was performed as `PHP_CS_FIXER_FUTURE_MODE` env var is set."); } @trigger_error($message, E_USER_DEPRECATED); } } } private function resolveFinder() { $this->configFinderIsOverridden = false; if ($this->isStdIn()) { return new \ArrayIterator([new StdinFileInfo()]); } $modes = [self::PATH_MODE_OVERRIDE, self::PATH_MODE_INTERSECTION]; if (!\in_array( $this->options['path-mode'], $modes, true )) { throw new InvalidConfigurationException(sprintf( 'The path-mode "%s" is not defined, supported are "%s".', $this->options['path-mode'], implode('", "', $modes) )); } $isIntersectionPathMode = self::PATH_MODE_INTERSECTION === $this->options['path-mode']; $paths = array_filter(array_map( static function ($path) { return realpath($path); }, $this->getPath() )); if (!\count($paths)) { if ($isIntersectionPathMode) { return new \ArrayIterator([]); } return $this->iterableToTraversable($this->getConfig()->getFinder()); } $pathsByType = [ 'file' => [], 'dir' => [], ]; foreach ($paths as $path) { if (is_file($path)) { $pathsByType['file'][] = $path; } else { $pathsByType['dir'][] = $path.\DIRECTORY_SEPARATOR; } } $nestedFinder = null; $currentFinder = $this->iterableToTraversable($this->getConfig()->getFinder()); try { $nestedFinder = $currentFinder instanceof \IteratorAggregate ? $currentFinder->getIterator() : $currentFinder; } catch (\Exception $e) { } if ($isIntersectionPathMode) { if (null === $nestedFinder) { throw new InvalidConfigurationException( 'Cannot create intersection with not-fully defined Finder in configuration file.' ); } return new \CallbackFilterIterator( new \IteratorIterator($nestedFinder), static function (\SplFileInfo $current) use ($pathsByType) { $currentRealPath = $current->getRealPath(); if (\in_array($currentRealPath, $pathsByType['file'], true)) { return true; } foreach ($pathsByType['dir'] as $path) { if (0 === strpos($currentRealPath, $path)) { return true; } } return false; } ); } if (null !== $this->getConfigFile() && null !== $nestedFinder) { $this->configFinderIsOverridden = true; } if ($currentFinder instanceof SymfonyFinder && null === $nestedFinder) { return $currentFinder->in($pathsByType['dir'])->append($pathsByType['file']); } return Finder::create()->in($pathsByType['dir'])->append($pathsByType['file']); } private function setOption($name, $value) { if (!\array_key_exists($name, $this->options)) { throw new InvalidConfigurationException(sprintf('Unknown option name: "%s".', $name)); } $this->options[$name] = $value; } private function resolveOptionBooleanValue($optionName) { $value = $this->options[$optionName]; if (\is_bool($value)) { return $value; } if (!\is_string($value)) { throw new InvalidConfigurationException(sprintf('Expected boolean or string value for option "%s".', $optionName)); } if ('yes' === $value) { return true; } if ('no' === $value) { return false; } $message = sprintf('Expected "yes" or "no" for option "%s", other values are deprecated and support will be removed in 3.0. Got "%s", this implicitly set the option to "false".', $optionName, $value); if (getenv('PHP_CS_FIXER_FUTURE_MODE')) { throw new InvalidConfigurationException("{$message} This check was performed as `PHP_CS_FIXER_FUTURE_MODE` env var is set."); } @trigger_error($message, E_USER_DEPRECATED); return false; } private static function separatedContextLessInclude($path) { return include $path; } } toolInfo = $toolInfo; } public function detectOldMajor() { } public function detectOldVendor() { if ($this->toolInfo->isInstalledByComposer()) { $details = $this->toolInfo->getComposerInstallationDetails(); if (ToolInfo::COMPOSER_LEGACY_PACKAGE_NAME === $details['name']) { $this->warnings[] = sprintf( 'You are running PHP CS Fixer installed with old vendor `%s`. Please update to `%s`.', ToolInfo::COMPOSER_LEGACY_PACKAGE_NAME, ToolInfo::COMPOSER_PACKAGE_NAME ); } } } public function getWarnings() { if (!\count($this->warnings)) { return []; } return array_unique(array_merge( $this->warnings, ['If you need help while solving warnings, ask at https://gitter.im/PHP-CS-Fixer, we will help you!'] )); } } versionChecker = $versionChecker; $this->toolInfo = $toolInfo; $this->pharChecker = $pharChecker; } protected function configure() { $this ->setAliases(['selfupdate']) ->setDefinition( [ new InputOption('--force', '-f', InputOption::VALUE_NONE, 'Force update to next major version if available.'), ] ) ->setDescription('Update php-cs-fixer.phar to the latest stable version.') ->setHelp( <<<'EOT' The %command.name% command replace your php-cs-fixer.phar by the latest version released on: https://github.com/FriendsOfPHP/PHP-CS-Fixer/releases $ php php-cs-fixer.phar %command.name% EOT ) ; } protected function execute(InputInterface $input, OutputInterface $output) { if (!$this->toolInfo->isInstalledAsPhar()) { $output->writeln('Self-update is available only for PHAR version.'); return 1; } $currentVersion = $this->getApplication()->getVersion(); Preg::match('/^v?(?\d+)\./', $currentVersion, $matches); $currentMajor = (int) $matches['major']; try { $latestVersion = $this->versionChecker->getLatestVersion(); $latestVersionOfCurrentMajor = $this->versionChecker->getLatestVersionOfMajor($currentMajor); } catch (\Exception $exception) { $output->writeln(sprintf( 'Unable to determine newest version: %s', $exception->getMessage() )); return 1; } if (1 !== $this->versionChecker->compareVersions($latestVersion, $currentVersion)) { $output->writeln('php-cs-fixer is already up to date.'); return 0; } $remoteTag = $latestVersion; if ( 0 !== $this->versionChecker->compareVersions($latestVersionOfCurrentMajor, $latestVersion) && true !== $input->getOption('force') ) { $output->writeln(sprintf('A new major version of php-cs-fixer is available (%s)', $latestVersion)); $output->writeln(sprintf('Before upgrading please read https://github.com/FriendsOfPHP/PHP-CS-Fixer/blob/%s/UPGRADE.md', $latestVersion)); $output->writeln('If you are ready to upgrade run this command with -f'); $output->writeln('Checking for new minor/patch version...'); if (1 !== $this->versionChecker->compareVersions($latestVersionOfCurrentMajor, $currentVersion)) { $output->writeln('No minor update for php-cs-fixer.'); return 0; } $remoteTag = $latestVersionOfCurrentMajor; } $localFilename = realpath($_SERVER['argv'][0]) ?: $_SERVER['argv'][0]; if (!is_writable($localFilename)) { $output->writeln(sprintf('No permission to update %s file.', $localFilename)); return 1; } $tempFilename = \dirname($localFilename).'/'.basename($localFilename, '.phar').'-tmp.phar'; $remoteFilename = $this->toolInfo->getPharDownloadUri($remoteTag); if (false === @copy($remoteFilename, $tempFilename)) { $output->writeln(sprintf('Unable to download new version %s from the server.', $remoteTag)); return 1; } chmod($tempFilename, 0777 & ~umask()); $pharInvalidityReason = $this->pharChecker->checkFileValidity($tempFilename); if (null !== $pharInvalidityReason) { unlink($tempFilename); $output->writeln(sprintf('The download of %s is corrupt (%s).', $remoteTag, $pharInvalidityReason)); $output->writeln('Please re-run the self-update command to try again.'); return 1; } rename($tempFilename, $localFilename); $output->writeln(sprintf('php-cs-fixer updated (%s)', $remoteTag)); return 0; } } defaultConfig = new Config(); $this->errorsManager = new ErrorsManager(); $this->eventDispatcher = new EventDispatcher(); $this->stopwatch = new Stopwatch(); $this->toolInfo = $toolInfo; } public function getHelp() { return HelpCommand::getHelpCopy(); } protected function configure() { $this ->setDefinition( [ new InputArgument('path', InputArgument::IS_ARRAY, 'The path.'), new InputOption('path-mode', '', InputOption::VALUE_REQUIRED, 'Specify path mode (can be override or intersection).', 'override'), new InputOption('allow-risky', '', InputOption::VALUE_REQUIRED, 'Are risky fixers allowed (can be yes or no).'), new InputOption('config', '', InputOption::VALUE_REQUIRED, 'The path to a .php_cs file.'), new InputOption('dry-run', '', InputOption::VALUE_NONE, 'Only shows which files would have been modified.'), new InputOption('rules', '', InputOption::VALUE_REQUIRED, 'The rules.'), new InputOption('using-cache', '', InputOption::VALUE_REQUIRED, 'Does cache should be used (can be yes or no).'), new InputOption('cache-file', '', InputOption::VALUE_REQUIRED, 'The path to the cache file.'), new InputOption('diff', '', InputOption::VALUE_NONE, 'Also produce diff for each file.'), new InputOption('diff-format', '', InputOption::VALUE_REQUIRED, 'Specify diff format.'), new InputOption('format', '', InputOption::VALUE_REQUIRED, 'To output results in other formats.'), new InputOption('stop-on-violation', '', InputOption::VALUE_NONE, 'Stop execution on first violation.'), new InputOption('show-progress', '', InputOption::VALUE_REQUIRED, 'Type of progress indicator (none, run-in, estimating, estimating-max or dots).'), ] ) ->setDescription('Fixes a directory or a file.') ; } protected function execute(InputInterface $input, OutputInterface $output) { $verbosity = $output->getVerbosity(); $passedConfig = $input->getOption('config'); $passedRules = $input->getOption('rules'); $resolver = new ConfigurationResolver( $this->defaultConfig, [ 'allow-risky' => $input->getOption('allow-risky'), 'config' => $passedConfig, 'dry-run' => $input->getOption('dry-run'), 'rules' => $passedRules, 'path' => $input->getArgument('path'), 'path-mode' => $input->getOption('path-mode'), 'using-cache' => $input->getOption('using-cache'), 'cache-file' => $input->getOption('cache-file'), 'format' => $input->getOption('format'), 'diff' => $input->getOption('diff'), 'diff-format' => $input->getOption('diff-format'), 'stop-on-violation' => $input->getOption('stop-on-violation'), 'verbosity' => $verbosity, 'show-progress' => $input->getOption('show-progress'), ], getcwd(), $this->toolInfo ); $reporter = $resolver->getReporter(); $stdErr = $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : ('txt' === $reporter->getFormat() ? $output : null) ; if (null !== $stdErr) { if (null !== $passedConfig && null !== $passedRules) { if (getenv('PHP_CS_FIXER_FUTURE_MODE')) { throw new \RuntimeException('Passing both `config` and `rules` options is not possible. This check was performed as `PHP_CS_FIXER_FUTURE_MODE` env var is set.'); } $stdErr->writeln([ sprintf($stdErr->isDecorated() ? '%s' : '%s', 'When passing both "--config" and "--rules" the rules within the configuration file are not used.'), sprintf($stdErr->isDecorated() ? '%s' : '%s', 'Passing both options is deprecated; version v3.0 PHP-CS-Fixer will exit with a configuration error code.'), ]); } $configFile = $resolver->getConfigFile(); $stdErr->writeln(sprintf('Loaded config %s%s.', $resolver->getConfig()->getName(), null === $configFile ? '' : ' from "'.$configFile.'"')); if ($resolver->getUsingCache()) { $cacheFile = $resolver->getCacheFile(); if (is_file($cacheFile)) { $stdErr->writeln(sprintf('Using cache file "%s".', $cacheFile)); } } } $progressType = $resolver->getProgress(); $finder = $resolver->getFinder(); if (null !== $stdErr && $resolver->configFinderIsOverridden()) { $stdErr->writeln( sprintf($stdErr->isDecorated() ? '%s' : '%s', 'Paths from configuration file have been overridden by paths provided as command arguments.') ); } if ('none' === $progressType || null === $stdErr) { $progressOutput = new NullOutput(); } elseif ('run-in' === $progressType) { $progressOutput = new ProcessOutput($stdErr, $this->eventDispatcher, null, null); } else { $finder = new \ArrayIterator(iterator_to_array($finder)); $progressOutput = new ProcessOutput( $stdErr, $this->eventDispatcher, 'estimating' !== $progressType ? (new Terminal())->getWidth() : null, \count($finder) ); } $runner = new Runner( $finder, $resolver->getFixers(), $resolver->getDiffer(), 'none' !== $progressType ? $this->eventDispatcher : null, $this->errorsManager, $resolver->getLinter(), $resolver->isDryRun(), $resolver->getCacheManager(), $resolver->getDirectory(), $resolver->shouldStopOnViolation() ); $this->stopwatch->start('fixFiles'); $changed = $runner->fix(); $this->stopwatch->stop('fixFiles'); $progressOutput->printLegend(); $fixEvent = $this->stopwatch->getEvent('fixFiles'); $reportSummary = new ReportSummary( $changed, $fixEvent->getDuration(), $fixEvent->getMemory(), OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity(), $resolver->isDryRun(), $output->isDecorated() ); $output->isDecorated() ? $output->write($reporter->generate($reportSummary)) : $output->write($reporter->generate($reportSummary), false, OutputInterface::OUTPUT_RAW) ; $invalidErrors = $this->errorsManager->getInvalidErrors(); $exceptionErrors = $this->errorsManager->getExceptionErrors(); $lintErrors = $this->errorsManager->getLintErrors(); if (null !== $stdErr) { $errorOutput = new ErrorOutput($stdErr); if (\count($invalidErrors) > 0) { $errorOutput->listErrors('linting before fixing', $invalidErrors); } if (\count($exceptionErrors) > 0) { $errorOutput->listErrors('fixing', $exceptionErrors); } if (\count($lintErrors) > 0) { $errorOutput->listErrors('linting after fixing', $lintErrors); } } $exitStatusCalculator = new FixCommandExitStatusCalculator(); return $exitStatusCalculator->calculate( $resolver->isDryRun(), \count($changed) > 0, \count($invalidErrors) > 0, \count($exceptionErrors) > 0, \count($lintErrors) > 0 ); } } %command.name% command tries to fix as much coding standards problems as possible on a given file or files in a given directory and its subdirectories: $ php %command.full_name% /path/to/dir $ php %command.full_name% /path/to/file By default --path-mode is set to ``override``, which means, that if you specify the path to a file or a directory via command arguments, then the paths provided to a ``Finder`` in config file will be ignored. You can use --path-mode=intersection to merge paths from the config file and from the argument: $ php %command.full_name% --path-mode=intersection /path/to/dir The --format option for the output format. Supported formats are ``txt`` (default one), ``json``, ``xml``, ``checkstyle``, ``junit`` and ``gitlab``. NOTE: the output for the following formats are generated in accordance with XML schemas * ``junit`` follows the `JUnit xml schema from Jenkins `_ * ``checkstyle`` follows the common `"checkstyle" xml schema `_ The --quiet Do not output any message. The --verbose option will show the applied rules. When using the ``txt`` format it will also display progress notifications. NOTE: if there is an error like "errors reported during linting after fixing", you can use this to be even more verbose for debugging purpose * ``--verbose=0`` or no option: normal * ``--verbose``, ``--verbose=1``, ``-v``: verbose * ``--verbose=2``, ``-vv``: very verbose * ``--verbose=3``, ``-vvv``: debug The --rules option limits the rules to apply to the project: $ php %command.full_name% /path/to/project --rules=@PSR2 By default the PSR1 and PSR2 rules are used. The --rules option lets you choose the exact rules to apply (the rule names must be separated by a comma): $ php %command.full_name% /path/to/dir --rules=line_ending,full_opening_tag,indentation_type You can also blacklist the rules you don't want by placing a dash in front of the rule name, if this is more convenient, using -name_of_fixer: $ php %command.full_name% /path/to/dir --rules=-full_opening_tag,-indentation_type When using combinations of exact and blacklist rules, applying exact rules along with above blacklisted results: $ php %command.full_name% /path/to/project --rules=@Symfony,-@PSR1,-blank_line_before_statement,strict_comparison Complete configuration for rules can be supplied using a ``json`` formatted string. $ php %command.full_name% /path/to/project --rules='{"concat_space": {"spacing": "none"}}' The --dry-run flag will run the fixer without making changes to your files. The --diff flag can be used to let the fixer output all the changes it makes. The --diff-format option allows to specify in which format the fixer should output the changes it makes: * udiff: unified diff format; * sbd: Sebastianbergmann/diff format (default when using `--diff` without specifying `diff-format`). The --allow-risky option (pass ``yes`` or ``no``) allows you to set whether risky rules may run. Default value is taken from config file. A rule is considered risky if it could change code behaviour. By default no risky rules are run. The --stop-on-violation flag stops the execution upon first file that needs to be fixed. The --show-progress option allows you to choose the way process progress is rendered: * none: disables progress output; * run-in: [deprecated] simple single-line progress output; * estimating: [deprecated] multiline progress output with number of files and percentage on each line. Note that with this option, the files list is evaluated before processing to get the total number of files and then kept in memory to avoid using the file iterator twice. This has an impact on memory usage so using this option is not recommended on very large projects; * estimating-max: [deprecated] same as dots; * dots: same as estimating but using all terminal columns instead of default 80. If the option is not provided, it defaults to run-in unless a config file that disables output is used, in which case it defaults to none. This option has no effect if the verbosity of the command is less than verbose. $ php %command.full_name% --verbose --show-progress=estimating The command can also read from standard input, in which case it won't automatically fix anything: $ cat foo.php | php %command.full_name% --diff - Finally, if you don't need BC kept on CLI level, you might use `PHP_CS_FIXER_FUTURE_MODE` to start using options that would be default in next MAJOR release (unified differ, estimating, full-width progress indicator): $ PHP_CS_FIXER_FUTURE_MODE=1 php %command.full_name% -v --diff Choose from the list of available rules: %%%FIXERS_DETAILS%%% The --dry-run option displays the files that need to be fixed but without actually modifying them: $ php %command.full_name% /path/to/code --dry-run Config file ----------- Instead of using command line options to customize the rule, you can save the project configuration in a .php_cs.dist file in the root directory of your project. The file must return an instance of `PhpCsFixer\ConfigInterface` (%%%CONFIG_INTERFACE_URL%%%) which lets you configure the rules, the files and directories that need to be analyzed. You may also create .php_cs file, which is the local configuration that will be used instead of the project configuration. It is a good practice to add that file into your .gitignore file. With the --config option you can specify the path to the .php_cs file. The example below will add two rules to the default list of PSR2 set rules: exclude('somedir') ->notPath('src/Symfony/Component/Translation/Tests/fixtures/resources.php') ->in(__DIR__) ; return PhpCsFixer\Config::create() ->setRules([ '@PSR2' => true, 'strict_param' => true, 'array_syntax' => ['syntax' => 'short'], ]) ->setFinder($finder) ; ?> **NOTE**: ``exclude`` will work only for directories, so if you need to exclude file, try ``notPath``. Both ``exclude`` and ``notPath`` methods accept only relative paths to the ones defined with the ``in`` method. See `Symfony\Finder` (https://symfony.com/doc/current/components/finder.html) online documentation for other `Finder` methods. You may also use a blacklist for the rules instead of the above shown whitelist approach. The following example shows how to use all ``Symfony`` rules but the ``full_opening_tag`` rule. exclude('somedir') ->in(__DIR__) ; return PhpCsFixer\Config::create() ->setRules([ '@Symfony' => true, 'full_opening_tag' => false, ]) ->setFinder($finder) ; ?> You may want to use non-linux whitespaces in your project. Then you need to configure them in your config file. setIndent("\t") ->setLineEnding("\r\n") ; ?> By using ``--using-cache`` option with ``yes`` or ``no`` you can set if the caching mechanism should be used. Caching ------- The caching mechanism is enabled by default. This will speed up further runs by fixing only files that were modified since the last run. The tool will fix all files if the tool version has changed or the list of rules has changed. Cache is supported only for tool downloaded as phar file or installed via composer. Cache can be disabled via ``--using-cache`` option or config file: setUsingCache(false) ; ?> Cache file can be specified via ``--cache-file`` option or config file: setCacheFile(__DIR__.'/.php_cs.cache') ; ?> Using PHP CS Fixer on CI ------------------------ Require ``friendsofphp/php-cs-fixer`` as a ``dev`` dependency: $ ./composer.phar require --dev friendsofphp/php-cs-fixer Then, add the following command to your CI: %%%CI_INTEGRATION%%% Where ``$COMMIT_RANGE`` is your range of commits, e.g. ``$TRAVIS_COMMIT_RANGE`` or ``HEAD~..HEAD``. Exit code --------- Exit code is built using following bit flags: * 0 - OK. * 1 - General error (or PHP minimal requirement not matched). * 4 - Some files have invalid syntax (only in dry-run mode). * 8 - Some files need fixing (only in dry-run mode). * 16 - Configuration error of the application. * 32 - Configuration error of a Fixer. * 64 - Exception raised within the application. (Applies to exit code of the ``fix`` command only) EOF ; return strtr($template, [ '%%%CONFIG_INTERFACE_URL%%%' => sprintf( 'https://github.com/FriendsOfPHP/PHP-CS-Fixer/blob/v%s/src/ConfigInterface.php', self::getLatestReleaseVersionFromChangeLog() ), '%%%CI_INTEGRATION%%%' => implode("\n", array_map( static function ($line) { return ' $ '.$line; }, \array_slice(file(__DIR__.'/../../../ci-integration.sh', FILE_IGNORE_NEW_LINES), 3) )), '%%%FIXERS_DETAILS%%%' => self::getFixersHelp(), ]); } public static function toString($value) { if (\is_array($value)) { static $replaces = [ ['#\r|\n#', '#\s{1,}#', '#array\s*\((.*)\)#s', '#\[\s+#', '#,\s*\]#', '#\d+\s*=>\s*#'], ['', ' ', '[$1]', '[', ']', ''], ]; $str = var_export($value, true); do { $strNew = Preg::replace( $replaces[0], $replaces[1], $str ); if ($strNew === $str) { break; } $str = $strNew; } while (true); } else { $str = var_export($value, true); } return Preg::replace('/\bNULL\b/', 'null', $str); } public static function getDisplayableAllowedValues(FixerOptionInterface $option) { $allowed = $option->getAllowedValues(); if (null !== $allowed) { $allowed = array_filter($allowed, static function ($value) { return !($value instanceof \Closure); }); usort($allowed, static function ($valueA, $valueB) { if ($valueA instanceof AllowedValueSubset) { return -1; } if ($valueB instanceof AllowedValueSubset) { return 1; } return strcasecmp( self::toString($valueA), self::toString($valueB) ); }); if (0 === \count($allowed)) { $allowed = null; } } return $allowed; } public static function getLatestReleaseVersionFromChangeLog() { static $version = null; if (null !== $version) { return $version; } $changelogFile = self::getChangeLogFile(); if (null === $changelogFile) { $version = Application::VERSION; return $version; } $changelog = @file_get_contents($changelogFile); if (false === $changelog) { $error = error_get_last(); throw new \RuntimeException(sprintf( 'Failed to read content of the changelog file "%s".%s', $changelogFile, $error ? ' '.$error['message'] : '' )); } for ($i = Application::getMajorVersion(); $i > 0; --$i) { if (1 === Preg::match('/Changelog for v('.$i.'.\d+.\d+)/', $changelog, $matches)) { $version = $matches[1]; break; } } if (null === $version) { throw new \RuntimeException(sprintf('Failed to parse changelog data of "%s".', $changelogFile)); } return $version; } protected function initialize(InputInterface $input, OutputInterface $output) { $output->getFormatter()->setStyle('url', new OutputFormatterStyle('blue')); } private static function getChangeLogFile() { $changelogFile = __DIR__.'/../../../CHANGELOG.md'; return is_file($changelogFile) ? $changelogFile : null; } private static function getFixersHelp() { $help = ''; $fixerFactory = new FixerFactory(); $fixers = $fixerFactory->registerBuiltInFixers()->getFixers(); usort( $fixers, static function (FixerInterface $a, FixerInterface $b) { return strcmp($a->getName(), $b->getName()); } ); $ruleSets = []; foreach (RuleSet::create()->getSetDefinitionNames() as $setName) { $ruleSets[$setName] = new RuleSet([$setName => true]); } $getSetsWithRule = static function ($rule) use ($ruleSets) { $sets = []; foreach ($ruleSets as $setName => $ruleSet) { if ($ruleSet->hasRule($rule)) { $sets[] = $setName; } } return $sets; }; $count = \count($fixers) - 1; foreach ($fixers as $i => $fixer) { $sets = $getSetsWithRule($fixer->getName()); $description = $fixer->getDefinition()->getSummary(); if ($fixer instanceof DeprecatedFixerInterface) { $successors = $fixer->getSuccessorsNames(); $message = [] === $successors ? 'will be removed on next major version' : sprintf('use %s instead', Utils::naturalLanguageJoinWithBackticks($successors)); $description .= sprintf(' DEPRECATED: %s.', $message); } $description = implode("\n | ", self::wordwrap( Preg::replace('/(`.+?`)/', '$1', $description), 72 )); if (!empty($sets)) { $help .= sprintf(" * %s [%s]\n | %s\n", $fixer->getName(), implode(', ', $sets), $description); } else { $help .= sprintf(" * %s\n | %s\n", $fixer->getName(), $description); } if ($fixer->isRisky()) { $help .= sprintf( " | *Risky rule: %s.*\n", Preg::replace( '/(`.+?`)/', '$1', lcfirst(Preg::replace('/\.$/', '', $fixer->getDefinition()->getRiskyDescription())) ) ); } if ($fixer instanceof ConfigurationDefinitionFixerInterface) { $configurationDefinition = $fixer->getConfigurationDefinition(); $configurationDefinitionOptions = $configurationDefinition->getOptions(); if (\count($configurationDefinitionOptions)) { $help .= " |\n | Configuration options:\n"; usort( $configurationDefinitionOptions, static function (FixerOptionInterface $optionA, FixerOptionInterface $optionB) { return strcmp($optionA->getName(), $optionB->getName()); } ); foreach ($configurationDefinitionOptions as $option) { $line = ''.OutputFormatter::escape($option->getName()).''; $allowed = self::getDisplayableAllowedValues($option); if (null !== $allowed) { foreach ($allowed as &$value) { if ($value instanceof AllowedValueSubset) { $value = 'a subset of '.self::toString($value->getAllowedValues()).''; } else { $value = ''.self::toString($value).''; } } } else { $allowed = array_map( static function ($type) { return ''.$type.''; }, $option->getAllowedTypes() ); } if (null !== $allowed) { $line .= ' ('.implode(', ', $allowed).')'; } $line .= ': '.Preg::replace( '/(`.+?`)/', '$1', lcfirst(Preg::replace('/\.$/', '', OutputFormatter::escape($option->getDescription()))) ).'; '; if ($option->hasDefault()) { $line .= 'defaults to '.self::toString($option->getDefault()).''; } else { $line .= 'required'; } if ($option instanceof DeprecatedFixerOption) { $line .= '. DEPRECATED: '.Preg::replace( '/(`.+?`)/', '$1', lcfirst(Preg::replace('/\.$/', '', OutputFormatter::escape($option->getDeprecationMessage()))) ); } if ($option instanceof AliasedFixerOption) { $line .= '; DEPRECATED alias: '.$option->getAlias().''; } foreach (self::wordwrap($line, 72) as $index => $line) { $help .= (0 === $index ? ' | - ' : ' | ').$line."\n"; } } } } elseif ($fixer instanceof ConfigurableFixerInterface) { $help .= " | *Configurable rule.*\n"; } if ($count !== $i) { $help .= "\n"; } } return Preg::replace('#\\\\()#', '<<$1', $help); } private static function wordwrap($string, $width) { $result = []; $currentLine = 0; $lineLength = 0; foreach (explode(' ', $string) as $word) { $wordLength = \strlen(Preg::replace('~~', '', $word)); if (0 !== $lineLength) { ++$wordLength; } if ($lineLength + $wordLength > $width) { ++$currentLine; $lineLength = 0; } $result[$currentLine][] = $word; $lineLength += $wordLength; } return array_map(static function ($line) { return implode(' ', $line); }, $result); } } name = $name; $this->type = $type; parent::__construct(); } public function getName() { return $this->name; } public function getType() { return $this->type; } } registerBuiltInFixers(); } $this->fixerFactory = $fixerFactory; } protected function configure() { $this ->setDefinition( [ new InputArgument('name', InputArgument::REQUIRED, 'Name of rule / set.'), ] ) ->setDescription('Describe rule / ruleset.') ; } protected function execute(InputInterface $input, OutputInterface $output) { $name = $input->getArgument('name'); try { if ('@' === $name[0]) { $this->describeSet($output, $name); return 0; } $this->describeRule($output, $name); } catch (DescribeNameNotFoundException $e) { $matcher = new WordMatcher( 'set' === $e->getType() ? $this->getSetNames() : array_keys($this->getFixers()) ); $alternative = $matcher->match($name); $this->describeList($output, $e->getType()); throw new \InvalidArgumentException(sprintf( '%s "%s" not found.%s', ucfirst($e->getType()), $name, null === $alternative ? '' : ' Did you mean "'.$alternative.'"?' )); } return 0; } private function describeRule(OutputInterface $output, $name) { $fixers = $this->getFixers(); if (!isset($fixers[$name])) { throw new DescribeNameNotFoundException($name, 'rule'); } $fixer = $fixers[$name]; if ($fixer instanceof DefinedFixerInterface) { $definition = $fixer->getDefinition(); } else { $definition = new FixerDefinition('Description is not available.', []); } $description = $definition->getSummary(); if ($fixer instanceof DeprecatedFixerInterface) { $successors = $fixer->getSuccessorsNames(); $message = [] === $successors ? 'will be removed on next major version' : sprintf('use %s instead', Utils::naturalLanguageJoinWithBackticks($successors)); $message = Preg::replace('/(`.+?`)/', '$1', $message); $description .= sprintf(' DEPRECATED: %s.', $message); } $output->writeln(sprintf('Description of %s rule.', $name)); if ($output->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE) { $output->writeln(sprintf('Fixer class: %s.', \get_class($fixer))); } $output->writeln($description); if ($definition->getDescription()) { $output->writeln($definition->getDescription()); } $output->writeln(''); if ($fixer->isRisky()) { $output->writeln('Fixer applying this rule is risky.'); if ($definition->getRiskyDescription()) { $output->writeln($definition->getRiskyDescription()); } $output->writeln(''); } if ($fixer instanceof ConfigurationDefinitionFixerInterface) { $configurationDefinition = $fixer->getConfigurationDefinition(); $options = $configurationDefinition->getOptions(); $output->writeln(sprintf('Fixer is configurable using following option%s:', 1 === \count($options) ? '' : 's')); foreach ($options as $option) { $line = '* '.OutputFormatter::escape($option->getName()).''; $allowed = HelpCommand::getDisplayableAllowedValues($option); if (null !== $allowed) { foreach ($allowed as &$value) { if ($value instanceof AllowedValueSubset) { $value = 'a subset of '.HelpCommand::toString($value->getAllowedValues()).''; } else { $value = ''.HelpCommand::toString($value).''; } } } else { $allowed = array_map( static function ($type) { return ''.$type.''; }, $option->getAllowedTypes() ); } if (null !== $allowed) { $line .= ' ('.implode(', ', $allowed).')'; } $description = Preg::replace('/(`.+?`)/', '$1', OutputFormatter::escape($option->getDescription())); $line .= ': '.lcfirst(Preg::replace('/\.$/', '', $description)).'; '; if ($option->hasDefault()) { $line .= sprintf( 'defaults to %s', HelpCommand::toString($option->getDefault()) ); } else { $line .= 'required'; } if ($option instanceof DeprecatedFixerOption) { $line .= '. DEPRECATED: '.Preg::replace( '/(`.+?`)/', '$1', OutputFormatter::escape(lcfirst($option->getDeprecationMessage())) ); } if ($option instanceof AliasedFixerOption) { $line .= '; DEPRECATED alias: '.$option->getAlias().''; } $output->writeln($line); } $output->writeln(''); } elseif ($fixer instanceof ConfigurableFixerInterface) { $output->writeln('Fixer is configurable.'); if ($definition->getConfigurationDescription()) { $output->writeln($definition->getConfigurationDescription()); } if ($definition->getDefaultConfiguration()) { $output->writeln(sprintf('Default configuration: %s.', HelpCommand::toString($definition->getDefaultConfiguration()))); } $output->writeln(''); } $codeSamples = array_filter($definition->getCodeSamples(), static function (CodeSampleInterface $codeSample) { if ($codeSample instanceof VersionSpecificCodeSampleInterface) { return $codeSample->isSuitableFor(\PHP_VERSION_ID); } return true; }); if (!\count($codeSamples)) { $output->writeln([ 'Fixing examples can not be demonstrated on the current PHP version.', '', ]); } else { $output->writeln('Fixing examples:'); $differ = new FullDiffer(); $diffFormatter = new DiffConsoleFormatter( $output->isDecorated(), sprintf( ' ---------- begin diff ----------%s%%s%s ----------- end diff -----------', PHP_EOL, PHP_EOL ) ); foreach ($codeSamples as $index => $codeSample) { $old = $codeSample->getCode(); $tokens = Tokens::fromCode($old); $configuration = $codeSample->getConfiguration(); if ($fixer instanceof ConfigurableFixerInterface) { $fixer->configure(null === $configuration ? [] : $configuration); } $file = $codeSample instanceof FileSpecificCodeSampleInterface ? $codeSample->getSplFileInfo() : new StdinFileInfo(); $fixer->fix($file, $tokens); $diff = $differ->diff($old, $tokens->generateCode()); if ($fixer instanceof ConfigurableFixerInterface) { if (null === $configuration) { $output->writeln(sprintf(' * Example #%d. Fixing with the default configuration.', $index + 1)); } else { $output->writeln(sprintf(' * Example #%d. Fixing with configuration: %s.', $index + 1, HelpCommand::toString($codeSample->getConfiguration()))); } } else { $output->writeln(sprintf(' * Example #%d.', $index + 1)); } $output->writeln($diffFormatter->format($diff, ' %s')); $output->writeln(''); } } } private function describeSet(OutputInterface $output, $name) { if (!\in_array($name, $this->getSetNames(), true)) { throw new DescribeNameNotFoundException($name, 'set'); } $ruleSet = new RuleSet([$name => true]); $rules = $ruleSet->getRules(); ksort($rules); $fixers = $this->getFixers(); $output->writeln(sprintf('Description of %s set.', $name)); $output->writeln(''); $help = ''; foreach ($rules as $rule => $config) { $fixer = $fixers[$rule]; if (!$fixer instanceof DefinedFixerInterface) { throw new \RuntimeException(sprintf( 'Cannot describe rule %s, the fixer does not implement %s', $rule, DefinedFixerInterface::class )); } $definition = $fixer->getDefinition(); $help .= sprintf( " * %s%s\n | %s\n%s\n", $rule, $fixer->isRisky() ? ' risky' : '', $definition->getSummary(), true !== $config ? sprintf(" | Configuration: %s\n", HelpCommand::toString($config)) : '' ); } $output->write($help); } private function getFixers() { if (null !== $this->fixers) { return $this->fixers; } $fixers = []; foreach ($this->fixerFactory->getFixers() as $fixer) { $fixers[$fixer->getName()] = $fixer; } $this->fixers = $fixers; ksort($this->fixers); return $this->fixers; } private function getSetNames() { if (null !== $this->setNames) { return $this->setNames; } $set = new RuleSet(); $this->setNames = $set->getSetDefinitionNames(); sort($this->setNames); return $this->setNames; } private function describeList(OutputInterface $output, $type) { if ($output->getVerbosity() >= OutputInterface::VERBOSITY_VERY_VERBOSE) { $describe = [ 'set' => $this->getSetNames(), 'rules' => $this->getFixers(), ]; } elseif ($output->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE) { $describe = 'set' === $type ? ['set' => $this->getSetNames()] : ['rules' => $this->getFixers()]; } else { return; } foreach ($describe as $list => $items) { $output->writeln(sprintf('Defined %s:', $list)); foreach ($items as $name => $item) { $output->writeln(sprintf('* %s', \is_string($name) ? $name : $item)); } } } } setDescription('Generates the README content, based on the fix command help.'); } protected function execute(InputInterface $input, OutputInterface $output) { $header = <<header('PHP Coding Standards Fixer', '=')} The PHP Coding Standards Fixer (PHP CS Fixer) tool fixes your code to follow standards; whether you want to follow PHP coding standards as defined in the PSR-1, PSR-2, etc., or other community driven ones like the Symfony one. You can **also** define your (team's) style through configuration. It can modernize your code (like converting the ``pow`` function to the ``**`` operator on PHP 5.6) and (micro) optimize it. If you are already using a linter to identify coding standards problems in your code, you know that fixing them by hand is tedious, especially on large projects. This tool does not only detect them, but also fixes them for you. The PHP CS Fixer is maintained on GitHub at https://github.com/FriendsOfPHP/PHP-CS-Fixer bug reports and ideas about new features are welcome there. You can talk to us at https://gitter.im/PHP-CS-Fixer/Lobby about the project, configuration, possible improvements, ideas and questions, please visit us! {$this->header('Requirements', '-')} PHP needs to be a minimum version of PHP 5.6.0. {$this->header('Installation', '-')} {$this->header('Locally', '~')} Download the `php-cs-fixer.phar`_ file and store it somewhere on your computer. {$this->header('Globally (manual)', '~')} You can run these commands to easily access latest ``php-cs-fixer`` from anywhere on your system: .. code-block:: bash $ wget %download.url% -O php-cs-fixer or with specified version: .. code-block:: bash $ wget %download.version_url% -O php-cs-fixer or with curl: .. code-block:: bash $ curl -L %download.url% -o php-cs-fixer then: .. code-block:: bash $ sudo chmod a+x php-cs-fixer $ sudo mv php-cs-fixer /usr/local/bin/php-cs-fixer Then, just run ``php-cs-fixer``. {$this->header('Globally (Composer)', '~')} To install PHP CS Fixer, `install Composer `_ and issue the following command: .. code-block:: bash $ composer global require friendsofphp/php-cs-fixer Then make sure you have the global Composer binaries directory in your ``PATH``. This directory is platform-dependent, see `Composer documentation `_ for details. Example for some Unix systems: .. code-block:: bash $ export PATH="\$PATH:\$HOME/.composer/vendor/bin" {$this->header('Globally (homebrew)', '~')} .. code-block:: bash $ brew install php-cs-fixer {$this->header('Locally (PHIVE)', '~')} Install `PHIVE `_ and issue the following command: .. code-block:: bash $ phive install php-cs-fixer # use `--global` for global install {$this->header('Update', '-')} {$this->header('Locally', '~')} The ``self-update`` command tries to update ``php-cs-fixer`` itself: .. code-block:: bash $ php php-cs-fixer.phar self-update {$this->header('Globally (manual)', '~')} You can update ``php-cs-fixer`` through this command: .. code-block:: bash $ sudo php-cs-fixer self-update {$this->header('Globally (Composer)', '~')} You can update ``php-cs-fixer`` through this command: .. code-block:: bash $ ./composer.phar global update friendsofphp/php-cs-fixer {$this->header('Globally (homebrew)', '~')} You can update ``php-cs-fixer`` through this command: .. code-block:: bash $ brew upgrade php-cs-fixer {$this->header('Locally (PHIVE)', '~')} .. code-block:: bash $ phive update php-cs-fixer {$this->header('Usage', '-')} EOF; $footer = <<header('Helpers', '-')} Dedicated plugins exist for: * `Atom`_ * `NetBeans`_ * `PhpStorm`_ * `Sublime Text`_ * `Vim`_ * `VS Code`_ {$this->header('Contribute', '-')} The tool comes with quite a few built-in fixers, but everyone is more than welcome to `contribute`_ more of them. {$this->header('Fixers', '~')} A *fixer* is a class that tries to fix one CS issue (a ``Fixer`` class must implement ``FixerInterface``). {$this->header('Configs', '~')} A *config* knows about the CS rules and the files and directories that must be scanned by the tool when run in the directory of your project. It is useful for projects that follow a well-known directory structures (like for Symfony projects for instance). .. _php-cs-fixer.phar: %download.url% .. _Atom: https://github.com/Glavin001/atom-beautify .. _NetBeans: http://plugins.netbeans.org/plugin/49042/php-cs-fixer .. _PhpStorm: https://medium.com/@valeryan/how-to-configure-phpstorm-to-use-php-cs-fixer-1844991e521f .. _Sublime Text: https://github.com/benmatselby/sublime-phpcs .. _Vim: https://github.com/stephpy/vim-php-cs-fixer .. _VS Code: https://github.com/junstyle/vscode-php-cs-fixer .. _contribute: https://github.com/FriendsOfPHP/PHP-CS-Fixer/blob/master/CONTRIBUTING.md EOF; $command = $this->getApplication()->get('fix'); $help = $command->getHelp(); $help = str_replace('%command.full_name%', 'php-cs-fixer.phar '.$command->getName(), $help); $help = str_replace('%command.name%', $command->getName(), $help); $help = Preg::replace('##', '``', $help); $help = Preg::replace('#`(``.+?``)`#', '$1', $help); $help = Preg::replace('#^(\s+)``(.+)``$#m', '$1$2', $help); $help = Preg::replace('#^ \* ``(.+)``(.*?\n)#m', "* **$1**$2\n", $help); $help = Preg::replace('#^ \\| #m', ' ', $help); $help = Preg::replace('#^ \\|#m', '', $help); $help = Preg::replace('#^(?= \\*Risky rule: )#m', "\n", $help); $help = Preg::replace("#^( Configuration options:\n)( - )#m", "$1\n$2", $help); $help = Preg::replace("#^\n( +\\$ )#m", "\n.. code-block:: bash\n\n$1", $help); $help = Preg::replace("#^\n( +<\\?php)#m", "\n.. code-block:: php\n\n$1", $help); $help = Preg::replaceCallback( '#^\s*<\?(\w+).*?\?>#ms', static function ($matches) { $result = Preg::replace("#^\\.\\. code-block:: bash\n\n#m", '', $matches[0]); if ('php' !== $matches[1]) { $result = Preg::replace("#<\\?{$matches[1]}\\s*#", '', $result); } return Preg::replace("#\n\n +\\?>#", '', $result); }, $help ); $help = Preg::replaceCallback( '#`(.+)`\s?\((.+)<\/url>\)#', static function (array $matches) { return sprintf('`%s <%s>`_', str_replace('\\', '\\\\', $matches[1]), $matches[2]); }, $help ); $help = Preg::replace('#^ #m', ' ', $help); $help = Preg::replace('#\*\* +\[#', '** [', $help); $downloadLatestUrl = sprintf('https://github.com/FriendsOfPHP/PHP-CS-Fixer/releases/download/v%s/php-cs-fixer.phar', HelpCommand::getLatestReleaseVersionFromChangeLog()); $downloadUrl = 'https://cs.symfony.com/download/php-cs-fixer-v2.phar'; $header = str_replace('%download.version_url%', $downloadLatestUrl, $header); $header = str_replace('%download.url%', $downloadUrl, $header); $footer = str_replace('%download.version_url%', $downloadLatestUrl, $footer); $footer = str_replace('%download.url%', $downloadUrl, $footer); $output->write($header."\n".$help."\n".$footer); return 0; } private function header($name, $underline) { return $name."\n".str_repeat($underline, \strlen($name)); } } true]; private $usingCache = true; public function __construct($name = 'default') { $this->name = $name; } public static function create() { return new static(); } public function getCacheFile() { return $this->cacheFile; } public function getCustomFixers() { return $this->customFixers; } public function getFinder() { if (null === $this->finder) { $this->finder = new Finder(); } return $this->finder; } public function getFormat() { return $this->format; } public function getHideProgress() { return $this->hideProgress; } public function getIndent() { return $this->indent; } public function getLineEnding() { return $this->lineEnding; } public function getName() { return $this->name; } public function getPhpExecutable() { return $this->phpExecutable; } public function getRiskyAllowed() { return $this->isRiskyAllowed; } public function getRules() { return $this->rules; } public function getUsingCache() { return $this->usingCache; } public function registerCustomFixers($fixers) { if (false === \is_array($fixers) && false === $fixers instanceof \Traversable) { throw new \InvalidArgumentException(sprintf( 'Argument must be an array or a Traversable, got "%s".', \is_object($fixers) ? \get_class($fixers) : \gettype($fixers) )); } foreach ($fixers as $fixer) { $this->addCustomFixer($fixer); } return $this; } public function setCacheFile($cacheFile) { $this->cacheFile = $cacheFile; return $this; } public function setFinder($finder) { if (false === \is_array($finder) && false === $finder instanceof \Traversable) { throw new \InvalidArgumentException(sprintf( 'Argument must be an array or a Traversable, got "%s".', \is_object($finder) ? \get_class($finder) : \gettype($finder) )); } $this->finder = $finder; return $this; } public function setFormat($format) { $this->format = $format; return $this; } public function setHideProgress($hideProgress) { $this->hideProgress = $hideProgress; return $this; } public function setIndent($indent) { $this->indent = $indent; return $this; } public function setLineEnding($lineEnding) { $this->lineEnding = $lineEnding; return $this; } public function setPhpExecutable($phpExecutable) { $this->phpExecutable = $phpExecutable; return $this; } public function setRiskyAllowed($isRiskyAllowed) { $this->isRiskyAllowed = $isRiskyAllowed; return $this; } public function setRules(array $rules) { $this->rules = $rules; return $this; } public function setUsingCache($usingCache) { $this->usingCache = $usingCache; return $this; } private function addCustomFixer(FixerInterface $fixer) { $this->customFixers[] = $fixer; } } [ 'encoding' => true, 'full_opening_tag' => true, ], '@PSR2' => [ '@PSR1' => true, 'blank_line_after_namespace' => true, 'braces' => true, 'class_definition' => true, 'elseif' => true, 'function_declaration' => true, 'indentation_type' => true, 'line_ending' => true, 'lowercase_constants' => true, 'lowercase_keywords' => true, 'method_argument_space' => ['on_multiline' => 'ensure_fully_multiline'], 'no_break_comment' => true, 'no_closing_tag' => true, 'no_spaces_after_function_name' => true, 'no_spaces_inside_parenthesis' => true, 'no_trailing_whitespace' => true, 'no_trailing_whitespace_in_comment' => true, 'single_blank_line_at_eof' => true, 'single_class_element_per_statement' => ['elements' => ['property']], 'single_import_per_statement' => true, 'single_line_after_imports' => true, 'switch_case_semicolon_to_colon' => true, 'switch_case_space' => true, 'visibility_required' => true, ], '@Symfony' => [ '@PSR2' => true, 'array_syntax' => ['syntax' => 'short'], 'binary_operator_spaces' => true, 'blank_line_after_opening_tag' => true, 'blank_line_before_statement' => [ 'statements' => ['return'], ], 'braces' => [ 'allow_single_line_closure' => true, ], 'cast_spaces' => true, 'class_attributes_separation' => ['elements' => ['method']], 'class_definition' => ['single_line' => true], 'concat_space' => true, 'declare_equal_normalize' => true, 'function_typehint_space' => true, 'include' => true, 'increment_style' => true, 'lowercase_cast' => true, 'lowercase_static_reference' => true, 'magic_constant_casing' => true, 'magic_method_casing' => true, 'method_argument_space' => true, 'native_function_casing' => true, 'native_function_type_declaration_casing' => true, 'new_with_braces' => true, 'no_blank_lines_after_class_opening' => true, 'no_blank_lines_after_phpdoc' => true, 'no_empty_comment' => true, 'no_empty_phpdoc' => true, 'no_empty_statement' => true, 'no_extra_blank_lines' => ['tokens' => [ 'curly_brace_block', 'extra', 'parenthesis_brace_block', 'square_brace_block', 'throw', 'use', ]], 'no_leading_import_slash' => true, 'no_leading_namespace_whitespace' => true, 'no_mixed_echo_print' => true, 'no_multiline_whitespace_around_double_arrow' => true, 'no_short_bool_cast' => true, 'no_singleline_whitespace_before_semicolons' => true, 'no_spaces_around_offset' => true, 'no_superfluous_phpdoc_tags' => ['allow_mixed' => true], 'no_trailing_comma_in_list_call' => true, 'no_trailing_comma_in_singleline_array' => true, 'no_unneeded_control_parentheses' => true, 'no_unneeded_curly_braces' => ['namespaces' => true], 'no_unused_imports' => true, 'no_whitespace_before_comma_in_array' => true, 'no_whitespace_in_blank_line' => true, 'normalize_index_brace' => true, 'object_operator_without_whitespace' => true, 'ordered_imports' => true, 'php_unit_fqcn_annotation' => true, 'phpdoc_align' => [ 'tags' => [ 'method', 'param', 'property', 'return', 'throws', 'type', 'var', ], ], 'phpdoc_annotation_without_dot' => true, 'phpdoc_indent' => true, 'phpdoc_inline_tag' => true, 'phpdoc_no_access' => true, 'phpdoc_no_alias_tag' => true, 'phpdoc_no_package' => true, 'phpdoc_no_useless_inheritdoc' => true, 'phpdoc_return_self_reference' => true, 'phpdoc_scalar' => true, 'phpdoc_separation' => true, 'phpdoc_single_line_var_spacing' => true, 'phpdoc_summary' => true, 'phpdoc_to_comment' => true, 'phpdoc_trim' => true, 'phpdoc_trim_consecutive_blank_line_separation' => true, 'phpdoc_types' => true, 'phpdoc_types_order' => [ 'null_adjustment' => 'always_last', 'sort_algorithm' => 'none', ], 'phpdoc_var_without_name' => true, 'return_type_declaration' => true, 'semicolon_after_instruction' => true, 'short_scalar_cast' => true, 'single_blank_line_before_namespace' => true, 'single_class_element_per_statement' => true, 'single_line_comment_style' => [ 'comment_types' => ['hash'], ], 'single_quote' => true, 'single_trait_insert_per_statement' => true, 'space_after_semicolon' => [ 'remove_in_empty_for_expressions' => true, ], 'standardize_increment' => true, 'standardize_not_equals' => true, 'ternary_operator_spaces' => true, 'trailing_comma_in_multiline_array' => true, 'trim_array_spaces' => true, 'unary_operator_spaces' => true, 'whitespace_after_comma_in_array' => true, 'yoda_style' => true, ], '@Symfony:risky' => [ 'dir_constant' => true, 'ereg_to_preg' => true, 'error_suppression' => true, 'fopen_flag_order' => true, 'fopen_flags' => ['b_mode' => false], 'function_to_constant' => [ 'functions' => [ 'get_called_class', 'get_class', 'get_class_this', 'php_sapi_name', 'phpversion', 'pi', ], ], 'implode_call' => true, 'is_null' => true, 'modernize_types_casting' => true, 'native_constant_invocation' => [ 'fix_built_in' => false, 'include' => [ 'DIRECTORY_SEPARATOR', 'PHP_SAPI', 'PHP_VERSION_ID', ], 'scope' => 'namespaced', ], 'native_function_invocation' => [ 'include' => [NativeFunctionInvocationFixer::SET_COMPILER_OPTIMIZED], 'scope' => 'namespaced', 'strict' => true, ], 'no_alias_functions' => true, 'no_homoglyph_names' => true, 'no_unneeded_final_method' => true, 'non_printable_character' => true, 'php_unit_construct' => true, 'php_unit_mock_short_will_return' => true, 'psr4' => true, 'self_accessor' => true, 'set_type_to_cast' => true, ], '@PhpCsFixer' => [ '@Symfony' => true, 'align_multiline_comment' => true, 'array_indentation' => true, 'blank_line_before_statement' => true, 'combine_consecutive_issets' => true, 'combine_consecutive_unsets' => true, 'compact_nullable_typehint' => true, 'escape_implicit_backslashes' => true, 'explicit_indirect_variable' => true, 'explicit_string_variable' => true, 'fully_qualified_strict_types' => true, 'heredoc_to_nowdoc' => true, 'method_argument_space' => ['on_multiline' => 'ensure_fully_multiline'], 'method_chaining_indentation' => true, 'multiline_comment_opening_closing' => true, 'multiline_whitespace_before_semicolons' => ['strategy' => 'new_line_for_chained_calls'], 'no_alternative_syntax' => true, 'no_binary_string' => true, 'no_extra_blank_lines' => ['tokens' => [ 'break', 'continue', 'curly_brace_block', 'extra', 'parenthesis_brace_block', 'return', 'square_brace_block', 'throw', 'use', ]], 'no_null_property_initialization' => true, 'no_short_echo_tag' => true, 'no_superfluous_elseif' => true, 'no_unset_cast' => true, 'no_useless_else' => true, 'no_useless_return' => true, 'ordered_class_elements' => true, 'php_unit_internal_class' => true, 'php_unit_method_casing' => true, 'php_unit_ordered_covers' => true, 'php_unit_test_class_requires_covers' => true, 'phpdoc_add_missing_param_annotation' => true, 'phpdoc_no_empty_return' => true, 'phpdoc_order' => true, 'phpdoc_types_order' => true, 'phpdoc_var_annotation_correct_order' => true, 'protected_to_private' => true, 'return_assignment' => true, 'simple_to_complex_string_variable' => true, 'single_line_comment_style' => true, ], '@PhpCsFixer:risky' => [ '@Symfony:risky' => true, 'comment_to_phpdoc' => true, 'final_internal_class' => true, 'logical_operators' => true, 'no_unreachable_default_argument_value' => true, 'no_unset_on_property' => true, 'php_unit_set_up_tear_down_visibility' => true, 'php_unit_strict' => true, 'php_unit_test_annotation' => true, 'php_unit_test_case_static_method_calls' => true, 'strict_comparison' => true, 'strict_param' => true, 'string_line_ending' => true, ], '@DoctrineAnnotation' => [ 'doctrine_annotation_array_assignment' => [ 'operator' => ':', ], 'doctrine_annotation_braces' => true, 'doctrine_annotation_indentation' => true, 'doctrine_annotation_spaces' => [ 'before_array_assignments_colon' => false, ], ], '@PHP56Migration' => [], '@PHP56Migration:risky' => [ 'pow_to_exponentiation' => true, ], '@PHP70Migration' => [ '@PHP56Migration' => true, 'ternary_to_null_coalescing' => true, ], '@PHP70Migration:risky' => [ '@PHP56Migration:risky' => true, 'combine_nested_dirname' => true, 'declare_strict_types' => true, 'non_printable_character' => [ 'use_escape_sequences_in_strings' => true, ], 'random_api_migration' => ['replacements' => [ 'mt_rand' => 'random_int', 'rand' => 'random_int', ]], ], '@PHP71Migration' => [ '@PHP70Migration' => true, 'visibility_required' => ['elements' => [ 'const', 'method', 'property', ]], ], '@PHP71Migration:risky' => [ '@PHP70Migration:risky' => true, 'void_return' => true, ], '@PHP73Migration' => [ '@PHP71Migration' => true, 'heredoc_indentation' => true, ], '@PHPUnit30Migration:risky' => [ 'php_unit_dedicate_assert' => ['target' => PhpUnitTargetVersion::VERSION_3_0], ], '@PHPUnit32Migration:risky' => [ '@PHPUnit30Migration:risky' => true, 'php_unit_no_expectation_annotation' => ['target' => PhpUnitTargetVersion::VERSION_3_2], ], '@PHPUnit35Migration:risky' => [ '@PHPUnit32Migration:risky' => true, 'php_unit_dedicate_assert' => ['target' => PhpUnitTargetVersion::VERSION_3_5], ], '@PHPUnit43Migration:risky' => [ '@PHPUnit35Migration:risky' => true, 'php_unit_no_expectation_annotation' => ['target' => PhpUnitTargetVersion::VERSION_4_3], ], '@PHPUnit48Migration:risky' => [ '@PHPUnit43Migration:risky' => true, 'php_unit_namespaced' => ['target' => PhpUnitTargetVersion::VERSION_4_8], ], '@PHPUnit50Migration:risky' => [ '@PHPUnit48Migration:risky' => true, 'php_unit_dedicate_assert' => true, ], '@PHPUnit52Migration:risky' => [ '@PHPUnit50Migration:risky' => true, 'php_unit_expectation' => ['target' => PhpUnitTargetVersion::VERSION_5_2], ], '@PHPUnit54Migration:risky' => [ '@PHPUnit52Migration:risky' => true, 'php_unit_mock' => ['target' => PhpUnitTargetVersion::VERSION_5_4], ], '@PHPUnit55Migration:risky' => [ '@PHPUnit54Migration:risky' => true, 'php_unit_mock' => ['target' => PhpUnitTargetVersion::VERSION_5_5], ], '@PHPUnit56Migration:risky' => [ '@PHPUnit55Migration:risky' => true, 'php_unit_dedicate_assert' => ['target' => PhpUnitTargetVersion::VERSION_5_6], 'php_unit_expectation' => ['target' => PhpUnitTargetVersion::VERSION_5_6], ], '@PHPUnit57Migration:risky' => [ '@PHPUnit56Migration:risky' => true, 'php_unit_namespaced' => ['target' => PhpUnitTargetVersion::VERSION_5_7], ], '@PHPUnit60Migration:risky' => [ '@PHPUnit57Migration:risky' => true, 'php_unit_namespaced' => ['target' => PhpUnitTargetVersion::VERSION_6_0], ], '@PHPUnit75Migration:risky' => [ '@PHPUnit60Migration:risky' => true, 'php_unit_dedicate_assert_internal_type' => ['target' => PhpUnitTargetVersion::VERSION_7_5], ], ]; private $set; private $rules; public function __construct(array $set = []) { foreach ($set as $key => $value) { if (\is_int($key)) { throw new \InvalidArgumentException(sprintf('Missing value for "%s" rule/set.', $value)); } } $this->set = $set; $this->resolveSet(); } public static function create(array $set = []) { return new self($set); } public function hasRule($rule) { return \array_key_exists($rule, $this->rules); } public function getRuleConfiguration($rule) { if (!$this->hasRule($rule)) { throw new \InvalidArgumentException(sprintf('Rule "%s" is not in the set.', $rule)); } if (true === $this->rules[$rule]) { return null; } return $this->rules[$rule]; } public function getRules() { return $this->rules; } public function getSetDefinitionNames() { return array_keys($this->setDefinitions); } private function getSetDefinition($name) { if (!isset($this->setDefinitions[$name])) { throw new \InvalidArgumentException(sprintf('Set "%s" does not exist.', $name)); } return $this->setDefinitions[$name]; } private function resolveSet() { $rules = $this->set; $resolvedRules = []; foreach ($rules as $name => $value) { if ('@' === $name[0]) { if (!\is_bool($value)) { throw new \UnexpectedValueException(sprintf('Nested rule set "%s" configuration must be a boolean.', $name)); } $set = $this->resolveSubset($name, $value); $resolvedRules = array_merge($resolvedRules, $set); } else { $resolvedRules[$name] = $value; } } $resolvedRules = array_filter($resolvedRules); $this->rules = $resolvedRules; return $this; } private function resolveSubset($setName, $setValue) { $rules = $this->getSetDefinition($setName); foreach ($rules as $name => $value) { if ('@' === $name[0]) { $set = $this->resolveSubset($name, $setValue); unset($rules[$name]); $rules = array_merge($rules, $set); } elseif (!$setValue) { $rules[$name] = false; } else { $rules[$name] = $value; } } return $rules; } } registerBuiltInTransformers(); usort($this->items, static function (TransformerInterface $a, TransformerInterface $b) { return Utils::cmpInt($b->getPriority(), $a->getPriority()); }); } public static function create() { static $instance = null; if (!$instance) { $instance = new self(); } return $instance; } public function transform(Tokens $tokens) { foreach ($this->items as $transformer) { foreach ($tokens as $index => $token) { $transformer->process($tokens, $token, $index); } } } private function registerTransformer(TransformerInterface $transformer) { if (\PHP_VERSION_ID >= $transformer->getRequiredPhpVersionId()) { $this->items[] = $transformer; } } private function registerBuiltInTransformers() { static $registered = false; if ($registered) { return; } $registered = true; foreach ($this->findBuiltInTransformers() as $transformer) { $this->registerTransformer($transformer); } } private function findBuiltInTransformers() { foreach (Finder::create()->files()->in(__DIR__.'/Transformer') as $file) { $relativeNamespace = $file->getRelativePath(); $class = __NAMESPACE__.'\\Transformer\\'.($relativeNamespace ? $relativeNamespace.'\\' : '').$file->getBasename('.php'); yield new $class(); } } } getUseMapFromTokens($tokens); foreach ($useMap as $shortName => $fullName) { $regex = '/^\\\\?'.preg_quote($fullName, '/').'$/'; if (Preg::match($regex, $typeName)) { return $shortName; } } $namespaces = $this->getNamespacesFromTokens($tokens); if (1 === \count($namespaces)) { foreach ($namespaces as $fullName) { $matches = []; $regex = '/^\\\\?'.preg_quote($fullName, '/').'\\\\(?P.+)$/'; if (Preg::match($regex, $typeName, $matches)) { return $matches['className']; } } } foreach ($useMap as $shortName => $fullName) { $matches = []; $regex = '/^\\\\?'.preg_quote($fullName, '/').'\\\\(?P.+)$/'; if (Preg::match($regex, $typeName, $matches)) { return $shortName.'\\'.$matches['className']; } } return $typeName; } private function getNamespacesFromTokens(Tokens $tokens) { return array_map(static function (NamespaceAnalysis $info) { return $info->getFullName(); }, (new NamespacesAnalyzer())->getDeclarations($tokens)); } private function getUseMapFromTokens(Tokens $tokens) { $map = []; foreach ((new NamespaceUsesAnalyzer())->getDeclarationsFromTokens($tokens) as $useDeclaration) { $map[$useDeclaration->getShortName()] = $useDeclaration->getFullName(); } return $map; } } getConstants()); } return $constants; } } getImportUseIndexes(); return $this->getDeclarations($tokens, $useIndexes); } private function getDeclarations(Tokens $tokens, array $useIndexes) { $uses = []; foreach ($useIndexes as $index) { $endIndex = $tokens->getNextTokenOfKind($index, [';', [T_CLOSE_TAG]]); $analysis = $this->parseDeclaration($tokens, $index, $endIndex); if ($analysis) { $uses[] = $analysis; } } return $uses; } private function parseDeclaration(Tokens $tokens, $startIndex, $endIndex) { $fullName = $shortName = ''; $aliased = false; $type = NamespaceUseAnalysis::TYPE_CLASS; for ($i = $startIndex; $i <= $endIndex; ++$i) { $token = $tokens[$i]; if ($token->equals(',') || $token->isGivenKind(CT::T_GROUP_IMPORT_BRACE_CLOSE)) { return null; } if ($token->isGivenKind(CT::T_FUNCTION_IMPORT)) { $type = NamespaceUseAnalysis::TYPE_FUNCTION; } elseif ($token->isGivenKind(CT::T_CONST_IMPORT)) { $type = NamespaceUseAnalysis::TYPE_CONSTANT; } if ($token->isWhitespace() || $token->isComment() || $token->isGivenKind(T_USE)) { continue; } if ($token->isGivenKind(T_STRING)) { $shortName = $token->getContent(); if (!$aliased) { $fullName .= $shortName; } } elseif ($token->isGivenKind(T_NS_SEPARATOR)) { $fullName .= $token->getContent(); } elseif ($token->isGivenKind(T_AS)) { $aliased = true; } } return new NamespaceUseAnalysis( trim($fullName), $shortName, $aliased, $startIndex, $endIndex, $type ); } } isGivenKind(T_NAMESPACE)) { continue; } $declarationEndIndex = $tokens->getNextTokenOfKind($index, [';', '{']); $namespace = trim($tokens->generatePartialCode($index + 1, $declarationEndIndex - 1)); $declarationParts = explode('\\', $namespace); $shortName = end($declarationParts); if ($tokens[$declarationEndIndex]->equals('{')) { $scopeEndIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $declarationEndIndex); } else { $scopeEndIndex = $tokens->getNextTokenOfKind($declarationEndIndex, [[T_NAMESPACE]]); if (null === $scopeEndIndex) { $scopeEndIndex = \count($tokens); } --$scopeEndIndex; } $namespaces[] = new NamespaceAnalysis( $namespace, $shortName, $index, $declarationEndIndex, $index, $scopeEndIndex ); $index = $scopeEndIndex; } if (0 === \count($namespaces)) { $namespaces[] = new NamespaceAnalysis('', '', 0, 0, 0, \count($tokens) - 1); } return $namespaces; } } fullName = $fullName; $this->shortName = $shortName; $this->isAliased = $isAliased; $this->startIndex = $startIndex; $this->endIndex = $endIndex; $this->type = $type; } public function getFullName() { return $this->fullName; } public function getShortName() { return $this->shortName; } public function isAliased() { return $this->isAliased; } public function getStartIndex() { return $this->startIndex; } public function getEndIndex() { return $this->endIndex; } public function isClass() { return self::TYPE_CLASS === $this->type; } public function isFunction() { return self::TYPE_FUNCTION === $this->type; } public function isConstant() { return self::TYPE_CONSTANT === $this->type; } } fullName = $fullName; $this->shortName = $shortName; $this->startIndex = $startIndex; $this->endIndex = $endIndex; $this->scopeStartIndex = $scopeStartIndex; $this->scopeEndIndex = $scopeEndIndex; } public function getFullName() { return $this->fullName; } public function getShortName() { return $this->shortName; } public function getStartIndex() { return $this->startIndex; } public function getEndIndex() { return $this->endIndex; } public function getScopeStartIndex() { return $this->scopeStartIndex; } public function getScopeEndIndex() { return $this->scopeEndIndex; } } name = $name; $this->nullable = false; if (0 === strpos($name, '?')) { $this->name = substr($name, 1); $this->nullable = true; } $this->startIndex = $startIndex; $this->endIndex = $endIndex; } public function getName() { return $this->name; } public function getStartIndex() { return $this->startIndex; } public function getEndIndex() { return $this->endIndex; } public function isReservedType() { return \in_array($this->name, self::$reservedTypes, true); } public function isNullable() { return $this->nullable; } } name = $name; $this->nameIndex = $nameIndex; $this->default = $default ?: null; $this->typeAnalysis = $typeAnalysis ?: null; } public function getDefault() { return $this->default; } public function hasDefault() { return null !== $this->default; } public function getName() { return $this->name; } public function getNameIndex() { return $this->nameIndex; } public function getTypeAnalysis() { return $this->typeAnalysis; } public function hasTypeAnalysis() { return null !== $this->typeAnalysis; } } getArguments($tokens, $openParenthesis, $closeParenthesis)); } public function getArguments(Tokens $tokens, $openParenthesis, $closeParenthesis) { $arguments = []; $firstSensibleToken = $tokens->getNextMeaningfulToken($openParenthesis); if ($tokens[$firstSensibleToken]->equals(')')) { return $arguments; } $paramContentIndex = $openParenthesis + 1; $argumentsStart = $paramContentIndex; for (; $paramContentIndex < $closeParenthesis; ++$paramContentIndex) { $token = $tokens[$paramContentIndex]; $blockDefinitionProbe = Tokens::detectBlockType($token); if (null !== $blockDefinitionProbe && true === $blockDefinitionProbe['isStart']) { $paramContentIndex = $tokens->findBlockEnd($blockDefinitionProbe['type'], $paramContentIndex); continue; } if ($token->equals(',')) { if ($tokens->getNextMeaningfulToken($paramContentIndex) === $closeParenthesis) { break; } $arguments[$argumentsStart] = $paramContentIndex - 1; $argumentsStart = $paramContentIndex + 1; } } $arguments[$argumentsStart] = $paramContentIndex - 1; return $arguments; } public function getArgumentInfo(Tokens $tokens, $argumentStart, $argumentEnd) { $info = [ 'default' => null, 'name' => null, 'name_index' => null, 'type' => null, 'type_index_start' => null, 'type_index_end' => null, ]; $sawName = false; for ($index = $argumentStart; $index <= $argumentEnd; ++$index) { $token = $tokens[$index]; if ($token->isComment() || $token->isWhitespace() || $token->isGivenKind(T_ELLIPSIS) || $token->equals('&')) { continue; } if ($token->isGivenKind(T_VARIABLE)) { $sawName = true; $info['name_index'] = $index; $info['name'] = $token->getContent(); continue; } if ($token->equals('=')) { continue; } if ($sawName) { $info['default'] .= $token->getContent(); } else { $info['type_index_start'] = ($info['type_index_start'] > 0) ? $info['type_index_start'] : $index; $info['type_index_end'] = $index; $info['type'] .= $token->getContent(); } } return new ArgumentAnalysis( $info['name'], $info['name_index'], $info['default'], $info['type'] ? new TypeAnalysis($info['type'], $info['type_index_start'], $info['type_index_end']) : null ); } } '', 'imports' => [], 'declarations' => []]; public function isGlobalFunctionCall(Tokens $tokens, $index) { if (!$tokens[$index]->isGivenKind(T_STRING)) { return false; } $nextIndex = $tokens->getNextMeaningfulToken($index); if (!$tokens[$nextIndex]->equals('(')) { return false; } $previousIsNamespaceSeparator = false; $prevIndex = $tokens->getPrevMeaningfulToken($index); if ($tokens[$prevIndex]->isGivenKind(T_NS_SEPARATOR)) { $previousIsNamespaceSeparator = true; $prevIndex = $tokens->getPrevMeaningfulToken($prevIndex); } if ($tokens[$prevIndex]->isGivenKind([T_DOUBLE_COLON, T_FUNCTION, CT::T_NAMESPACE_OPERATOR, T_NEW, T_OBJECT_OPERATOR, CT::T_RETURN_REF, T_STRING])) { return false; } if ($previousIsNamespaceSeparator) { return true; } if ($tokens->isChanged() || $tokens->getCodeHash() !== $this->functionsAnalysis['tokens']) { $this->buildFunctionsAnalysis($tokens); } $namespaceAnalyzer = new NamespacesAnalyzer(); $declarations = $namespaceAnalyzer->getDeclarations($tokens); $scopeStartIndex = 0; $scopeEndIndex = \count($tokens) - 1; $inGlobalNamespace = false; foreach ($declarations as $declaration) { $scopeStartIndex = $declaration->getScopeStartIndex(); $scopeEndIndex = $declaration->getScopeEndIndex(); if ($index >= $scopeStartIndex && $index <= $scopeEndIndex) { $inGlobalNamespace = '' === $declaration->getFullName(); break; } } $call = strtolower($tokens[$index]->getContent()); if (!$inGlobalNamespace) { foreach ($this->functionsAnalysis['declarations'] as $functionNameIndex) { if ($functionNameIndex < $scopeStartIndex || $functionNameIndex > $scopeEndIndex) { continue; } if (strtolower($tokens[$functionNameIndex]->getContent()) === $call) { return false; } } } foreach ($this->functionsAnalysis['imports'] as $functionUse) { if ($functionUse->getStartIndex() < $scopeStartIndex || $functionUse->getEndIndex() > $scopeEndIndex) { continue; } if ($call !== strtolower($functionUse->getShortName())) { continue; } return $functionUse->getShortName() === ltrim($functionUse->getFullName(), '\\'); } return true; } public function getFunctionArguments(Tokens $tokens, $methodIndex) { $argumentsStart = $tokens->getNextTokenOfKind($methodIndex, ['(']); $argumentsEnd = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $argumentsStart); $argumentAnalyzer = new ArgumentsAnalyzer(); $arguments = []; foreach ($argumentAnalyzer->getArguments($tokens, $argumentsStart, $argumentsEnd) as $start => $end) { $argumentInfo = $argumentAnalyzer->getArgumentInfo($tokens, $start, $end); $arguments[$argumentInfo->getName()] = $argumentInfo; } return $arguments; } public function getFunctionReturnType(Tokens $tokens, $methodIndex) { $argumentsStart = $tokens->getNextTokenOfKind($methodIndex, ['(']); $argumentsEnd = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $argumentsStart); $typeColonIndex = $tokens->getNextMeaningfulToken($argumentsEnd); if (':' !== $tokens[$typeColonIndex]->getContent()) { return null; } $type = ''; $typeStartIndex = $tokens->getNextMeaningfulToken($typeColonIndex); $typeEndIndex = $typeStartIndex; $functionBodyStart = $tokens->getNextTokenOfKind($typeColonIndex, ['{', ';', [T_DOUBLE_ARROW]]); for ($i = $typeStartIndex; $i < $functionBodyStart; ++$i) { if ($tokens[$i]->isWhitespace() || $tokens[$i]->isComment()) { continue; } $type .= $tokens[$i]->getContent(); $typeEndIndex = $i; } return new TypeAnalysis($type, $typeStartIndex, $typeEndIndex); } public function isTheSameClassCall(Tokens $tokens, $index) { if (!$tokens->offsetExists($index)) { return false; } $operatorIndex = $tokens->getPrevMeaningfulToken($index); if (!$tokens->offsetExists($operatorIndex)) { return false; } $referenceIndex = $tokens->getPrevMeaningfulToken($operatorIndex); if (!$tokens->offsetExists($referenceIndex)) { return false; } return $tokens[$operatorIndex]->equals([T_OBJECT_OPERATOR, '->']) && $tokens[$referenceIndex]->equals([T_VARIABLE, '$this'], false) || $tokens[$operatorIndex]->equals([T_DOUBLE_COLON, '::']) && $tokens[$referenceIndex]->equals([T_STRING, 'self'], false) || $tokens[$operatorIndex]->equals([T_DOUBLE_COLON, '::']) && $tokens[$referenceIndex]->equals([T_STATIC, 'static'], false); } private function buildFunctionsAnalysis(Tokens $tokens) { $this->functionsAnalysis = [ 'tokens' => $tokens->getCodeHash(), 'imports' => [], 'declarations' => [], ]; if ($tokens->isTokenKindFound(T_FUNCTION)) { $end = \count($tokens); for ($i = 0; $i < $end; ++$i) { if ($tokens[$i]->isGivenKind(Token::getClassyTokenKinds())) { $i = $tokens->getNextTokenOfKind($i, ['(', '{']); if ($tokens[$i]->equals('(')) { $i = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $i); $i = $tokens->getNextTokenOfKind($i, ['{']); } $i = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $i); continue; } if (!$tokens[$i]->isGivenKind(T_FUNCTION)) { continue; } $i = $tokens->getNextMeaningfulToken($i); if ($tokens[$i]->isGivenKind(CT::T_RETURN_REF)) { $i = $tokens->getNextMeaningfulToken($i); } if (!$tokens[$i]->isGivenKind(T_STRING)) { continue; } $this->functionsAnalysis['declarations'][] = $i; } } $namespaceUsesAnalyzer = new NamespaceUsesAnalyzer(); if ($tokens->isTokenKindFound(CT::T_FUNCTION_IMPORT)) { $declarations = $namespaceUsesAnalyzer->getDeclarationsFromTokens($tokens); foreach ($declarations as $declaration) { if ($declaration->isFunction()) { $this->functionsAnalysis['imports'][] = $declaration; } } } } } isGivenKind([T_COMMENT, T_DOC_COMMENT])) { throw new \InvalidArgumentException('Given index must point to a comment.'); } if (null === $tokens->getNextMeaningfulToken($index)) { return false; } $prevIndex = $tokens->getPrevNonWhitespace($index); if ($tokens[$prevIndex]->equals(';')) { $braceCloseIndex = $tokens->getPrevMeaningfulToken($prevIndex); if (!$tokens[$braceCloseIndex]->equals(')')) { return false; } $braceOpenIndex = $tokens->findBlockStart(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $braceCloseIndex); $declareIndex = $tokens->getPrevMeaningfulToken($braceOpenIndex); if (!$tokens[$declareIndex]->isGivenKind(T_DECLARE)) { return false; } $prevIndex = $tokens->getPrevNonWhitespace($declareIndex); } return $tokens[$prevIndex]->isGivenKind(T_OPEN_TAG); } public function isBeforeStructuralElement(Tokens $tokens, $index) { $token = $tokens[$index]; if (!$token->isGivenKind([T_COMMENT, T_DOC_COMMENT])) { throw new \InvalidArgumentException('Given index must point to a comment.'); } $nextIndex = $index; do { $nextIndex = $tokens->getNextMeaningfulToken($nextIndex); } while (null !== $nextIndex && $tokens[$nextIndex]->equals('(')); if (null === $nextIndex || $tokens[$nextIndex]->equals('}')) { return false; } $nextToken = $tokens[$nextIndex]; if ($this->isStructuralElement($nextToken)) { return true; } if ($this->isValidControl($tokens, $token, $nextIndex)) { return true; } if ($this->isValidVariable($tokens, $nextIndex)) { return true; } if ($this->isValidLanguageConstruct($tokens, $token, $nextIndex)) { return true; } return false; } public function getCommentBlockIndices(Tokens $tokens, $index) { if (!$tokens[$index]->isGivenKind(T_COMMENT)) { throw new \InvalidArgumentException('Given index must point to a comment.'); } $commentType = $this->getCommentType($tokens[$index]->getContent()); $indices = [$index]; if (self::TYPE_SLASH_ASTERISK === $commentType) { return $indices; } $count = \count($tokens); ++$index; for (; $index < $count; ++$index) { if ($tokens[$index]->isComment()) { if ($commentType === $this->getCommentType($tokens[$index]->getContent())) { $indices[] = $index; continue; } break; } if (!$tokens[$index]->isWhitespace() || $this->getLineBreakCount($tokens, $index, $index + 1) > 1) { break; } } return $indices; } private function isStructuralElement(Token $token) { static $skip = [ T_PRIVATE, T_PROTECTED, T_PUBLIC, T_VAR, T_FUNCTION, T_ABSTRACT, T_CONST, T_NAMESPACE, T_REQUIRE, T_REQUIRE_ONCE, T_INCLUDE, T_INCLUDE_ONCE, T_FINAL, T_STATIC, ]; return $token->isClassy() || $token->isGivenKind($skip); } private function isValidControl(Tokens $tokens, Token $docsToken, $controlIndex) { static $controlStructures = [ T_FOR, T_FOREACH, T_IF, T_SWITCH, T_WHILE, ]; if (!$tokens[$controlIndex]->isGivenKind($controlStructures)) { return false; } $index = $tokens->getNextMeaningfulToken($controlIndex); $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index); $docsContent = $docsToken->getContent(); for ($index = $index + 1; $index < $endIndex; ++$index) { $token = $tokens[$index]; if ( $token->isGivenKind(T_VARIABLE) && false !== strpos($docsContent, $token->getContent()) ) { return true; } } return false; } private function isValidLanguageConstruct(Tokens $tokens, Token $docsToken, $languageConstructIndex) { static $languageStructures = [ T_LIST, T_PRINT, T_ECHO, CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN, ]; if (!$tokens[$languageConstructIndex]->isGivenKind($languageStructures)) { return false; } $endKind = $tokens[$languageConstructIndex]->isGivenKind(CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN) ? [CT::T_DESTRUCTURING_SQUARE_BRACE_CLOSE] : ')'; $endIndex = $tokens->getNextTokenOfKind($languageConstructIndex, [$endKind]); $docsContent = $docsToken->getContent(); for ($index = $languageConstructIndex + 1; $index < $endIndex; ++$index) { $token = $tokens[$index]; if ($token->isGivenKind(T_VARIABLE) && false !== strpos($docsContent, $token->getContent())) { return true; } } return false; } private function isValidVariable(Tokens $tokens, $index) { if (!$tokens[$index]->isGivenKind(T_VARIABLE)) { return false; } $nextIndex = $tokens->getNextMeaningfulToken($index); return $tokens[$nextIndex]->equals('='); } private function getCommentType($content) { if ('#' === $content[0]) { return self::TYPE_HASH; } if ('*' === $content[1]) { return self::TYPE_SLASH_ASTERISK; } return self::TYPE_DOUBLE_SLASH; } private function getLineBreakCount(Tokens $tokens, $whiteStart, $whiteEnd) { $lineCount = 0; for ($i = $whiteStart; $i < $whiteEnd; ++$i) { $lineCount += Preg::matchAll('/\R/u', $tokens[$i]->getContent()); } return $lineCount; } } offsetExists($openIndex)) { return false; } if (!$tokens->offsetExists($closeIndex)) { return false; } $blockType = $this->getBlockType($tokens[$openIndex]); if (null === $blockType) { return false; } return $closeIndex === $tokens->findBlockEnd($blockType, $openIndex); } private function getBlockType(Token $token) { foreach (Tokens::getBlockEdgeDefinitions() as $blockType => $definition) { if ($token->equals($definition['start'])) { return $blockType; } } return null; } } = 70400) { $prevKinds[] = T_FN; } if ( $token->equals('&') && $tokens[$tokens->getPrevMeaningfulToken($index)]->isGivenKind($prevKinds) ) { $tokens[$index] = new Token([CT::T_RETURN_REF, '&']); } } } equalsAny([ [T_CLASS, 'class'], [T_STRING, 'class'], ], false)) { return; } $prevIndex = $tokens->getPrevMeaningfulToken($index); $prevToken = $tokens[$prevIndex]; if ($prevToken->isGivenKind(T_DOUBLE_COLON)) { $tokens[$index] = new Token([CT::T_CLASS_CONSTANT, $token->getContent()]); } } } equals('|')) { return; } $prevIndex = $tokens->getPrevMeaningfulToken($index); $prevToken = $tokens[$prevIndex]; if (!$prevToken->isGivenKind(T_STRING)) { return; } do { $prevIndex = $tokens->getPrevMeaningfulToken($prevIndex); if (null === $prevIndex) { break; } $prevToken = $tokens[$prevIndex]; if ($prevToken->isGivenKind([T_NS_SEPARATOR, T_STRING])) { continue; } if ( $prevToken->isGivenKind(CT::T_TYPE_ALTERNATION) || ( $prevToken->equals('(') && $tokens[$tokens->getPrevMeaningfulToken($prevIndex)]->isGivenKind(T_CATCH) ) ) { $tokens[$index] = new Token([CT::T_TYPE_ALTERNATION, '|']); } break; } while (true); } } isGivenKind([T_CONST, T_FUNCTION])) { return; } $prevToken = $tokens[$tokens->getPrevMeaningfulToken($index)]; if ($prevToken->isGivenKind(T_USE)) { $tokens[$index] = new Token([ $token->isGivenKind(T_FUNCTION) ? CT::T_FUNCTION_IMPORT : CT::T_CONST_IMPORT, $token->getContent(), ]); } } } equals('(') || !$tokens[$tokens->getNextMeaningfulToken($index)]->equals([T_NEW])) { return; } if ($tokens[$tokens->getPrevMeaningfulToken($index)]->equalsAny([ ']', [CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE], [CT::T_ARRAY_SQUARE_BRACE_CLOSE], [T_ARRAY], [T_CLASS], [T_ELSEIF], [T_FOR], [T_FOREACH], [T_IF], [T_STATIC], [T_STRING], [T_SWITCH], [T_VARIABLE], [T_WHILE], ])) { return; } $closeIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index); $tokens[$index] = new Token([CT::T_BRACE_CLASS_INSTANTIATION_OPEN, '(']); $tokens[$closeIndex] = new Token([CT::T_BRACE_CLASS_INSTANTIATION_CLOSE, ')']); } } isGivenKind(T_USE) && $this->isUseForLambda($tokens, $index)) { $tokens[$index] = new Token([CT::T_USE_LAMBDA, $token->getContent()]); return; } if (!$token->isGivenKind([T_CLASS, T_TRAIT])) { return; } if ($tokens[$tokens->getPrevMeaningfulToken($index)]->isGivenKind(T_DOUBLE_COLON)) { return; } $index = $tokens->getNextTokenOfKind($index, ['{']); $innerLimit = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $index); while ($index < $innerLimit) { $token = $tokens[++$index]; if (!$token->isGivenKind(T_USE)) { continue; } if ($this->isUseForLambda($tokens, $index)) { $tokens[$index] = new Token([CT::T_USE_LAMBDA, $token->getContent()]); } else { $tokens[$index] = new Token([CT::T_USE_TRAIT, $token->getContent()]); } } } private function isUseForLambda(Tokens $tokens, $index) { $nextToken = $tokens[$tokens->getNextMeaningfulToken($index)]; return $nextToken->equals('('); } } isArrayDestructing($tokens, $index)) { $this->transformIntoDestructuringSquareBrace($tokens, $index); return; } if ($this->isShortArray($tokens, $index)) { $this->transformIntoArraySquareBrace($tokens, $index); } } private function transformIntoArraySquareBrace(Tokens $tokens, $index) { $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_INDEX_SQUARE_BRACE, $index); $tokens[$index] = new Token([CT::T_ARRAY_SQUARE_BRACE_OPEN, '[']); $tokens[$endIndex] = new Token([CT::T_ARRAY_SQUARE_BRACE_CLOSE, ']']); } private function transformIntoDestructuringSquareBrace(Tokens $tokens, $index) { $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_INDEX_SQUARE_BRACE, $index); $tokens[$index] = new Token([CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN, '[']); $tokens[$endIndex] = new Token([CT::T_DESTRUCTURING_SQUARE_BRACE_CLOSE, ']']); $previousMeaningfulIndex = $index; $index = $tokens->getNextMeaningfulToken($index); while ($index < $endIndex) { if ($tokens[$index]->equals('[') && $tokens[$previousMeaningfulIndex]->equalsAny([[CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN], ','])) { $tokens[$tokens->findBlockEnd(Tokens::BLOCK_TYPE_INDEX_SQUARE_BRACE, $index)] = new Token([CT::T_DESTRUCTURING_SQUARE_BRACE_CLOSE, ']']); $tokens[$index] = new Token([CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN, '[']); } $previousMeaningfulIndex = $index; $index = $tokens->getNextMeaningfulToken($index); } } private function isShortArray(Tokens $tokens, $index) { if (!$tokens[$index]->equals('[')) { return false; } static $disallowedPrevTokens = [ ')', ']', '}', '"', [T_CONSTANT_ENCAPSED_STRING], [T_STRING], [T_STRING_VARNAME], [T_VARIABLE], [CT::T_ARRAY_SQUARE_BRACE_CLOSE], [CT::T_DYNAMIC_PROP_BRACE_CLOSE], [CT::T_DYNAMIC_VAR_BRACE_CLOSE], [CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE], ]; $prevToken = $tokens[$tokens->getPrevMeaningfulToken($index)]; if ($prevToken->equalsAny($disallowedPrevTokens)) { return false; } $nextToken = $tokens[$tokens->getNextMeaningfulToken($index)]; if ($nextToken->equals(']')) { return true; } return !$this->isArrayDestructing($tokens, $index); } private function isArrayDestructing(Tokens $tokens, $index) { if (\PHP_VERSION_ID < 70100 || !$tokens[$index]->equals('[')) { return false; } static $disallowedPrevTokens = [ ')', ']', '"', [T_CONSTANT_ENCAPSED_STRING], [T_STRING], [T_STRING_VARNAME], [T_VARIABLE], [CT::T_ARRAY_SQUARE_BRACE_CLOSE], [CT::T_DYNAMIC_PROP_BRACE_CLOSE], [CT::T_DYNAMIC_VAR_BRACE_CLOSE], [CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE], ]; $prevToken = $tokens[$tokens->getPrevMeaningfulToken($index)]; if ($prevToken->equalsAny($disallowedPrevTokens)) { return false; } $type = Tokens::detectBlockType($tokens[$index]); $end = $tokens->findBlockEnd($type['type'], $index); $nextToken = $tokens[$tokens->getNextMeaningfulToken($end)]; return $nextToken->equals('='); } } isGivenKind(T_ARRAY)) { return; } $nextIndex = $tokens->getNextMeaningfulToken($index); $nextToken = $tokens[$nextIndex]; if (!$nextToken->equals('(')) { $tokens[$index] = new Token([CT::T_ARRAY_TYPEHINT, $token->getContent()]); } } } isGivenKind(T_NAMESPACE)) { return; } $nextIndex = $tokens->getNextMeaningfulToken($index); $nextToken = $tokens[$nextIndex]; if ($nextToken->isGivenKind(T_NS_SEPARATOR)) { $tokens[$index] = new Token([CT::T_NAMESPACE_OPERATOR, $token->getContent()]); } } } transformIntoCurlyCloseBrace($tokens, $token, $index); $this->transformIntoDollarCloseBrace($tokens, $token, $index); $this->transformIntoDynamicPropBraces($tokens, $token, $index); $this->transformIntoDynamicVarBraces($tokens, $token, $index); $this->transformIntoCurlyIndexBraces($tokens, $token, $index); if (\PHP_VERSION_ID >= 70000) { $this->transformIntoGroupUseBraces($tokens, $token, $index); } } private function transformIntoCurlyCloseBrace(Tokens $tokens, Token $token, $index) { if (!$token->isGivenKind(T_CURLY_OPEN)) { return; } $level = 1; $nestIndex = $index; while (0 < $level) { ++$nestIndex; if ($tokens[$nestIndex]->equals('{')) { ++$level; continue; } if ($tokens[$nestIndex]->equals('}')) { --$level; } } $tokens[$nestIndex] = new Token([CT::T_CURLY_CLOSE, '}']); } private function transformIntoDollarCloseBrace(Tokens $tokens, Token $token, $index) { if ($token->isGivenKind(T_DOLLAR_OPEN_CURLY_BRACES)) { $nextIndex = $tokens->getNextTokenOfKind($index, ['}']); $tokens[$nextIndex] = new Token([CT::T_DOLLAR_CLOSE_CURLY_BRACES, '}']); } } private function transformIntoDynamicPropBraces(Tokens $tokens, Token $token, $index) { if (!$token->isGivenKind(T_OBJECT_OPERATOR)) { return; } if (!$tokens[$index + 1]->equals('{')) { return; } $openIndex = $index + 1; $closeIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $openIndex); $tokens[$openIndex] = new Token([CT::T_DYNAMIC_PROP_BRACE_OPEN, '{']); $tokens[$closeIndex] = new Token([CT::T_DYNAMIC_PROP_BRACE_CLOSE, '}']); } private function transformIntoDynamicVarBraces(Tokens $tokens, Token $token, $index) { if (!$token->equals('$')) { return; } $openIndex = $tokens->getNextMeaningfulToken($index); if (null === $openIndex) { return; } $openToken = $tokens[$openIndex]; if (!$openToken->equals('{')) { return; } $closeIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $openIndex); $tokens[$openIndex] = new Token([CT::T_DYNAMIC_VAR_BRACE_OPEN, '{']); $tokens[$closeIndex] = new Token([CT::T_DYNAMIC_VAR_BRACE_CLOSE, '}']); } private function transformIntoCurlyIndexBraces(Tokens $tokens, Token $token, $index) { if (!$token->equals('{')) { return; } $prevIndex = $tokens->getPrevMeaningfulToken($index); if (!$tokens[$prevIndex]->equalsAny([ [T_STRING], [T_VARIABLE], [CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE], ']', ')', ])) { return; } if ( $tokens[$prevIndex]->isGivenKind(T_STRING) && !$tokens[$tokens->getPrevMeaningfulToken($prevIndex)]->isGivenKind(T_OBJECT_OPERATOR) ) { return; } if ( $tokens[$prevIndex]->equals(')') && !$tokens[$tokens->getPrevMeaningfulToken( $tokens->findBlockStart(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $prevIndex) )]->isGivenKind(T_ARRAY) ) { return; } $closeIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $index); $tokens[$index] = new Token([CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN, '{']); $tokens[$closeIndex] = new Token([CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE, '}']); } private function transformIntoGroupUseBraces(Tokens $tokens, Token $token, $index) { if (!$token->equals('{')) { return; } $prevIndex = $tokens->getPrevMeaningfulToken($index); if (!$tokens[$prevIndex]->isGivenKind(T_NS_SEPARATOR)) { return; } $closeIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $index); $tokens[$index] = new Token([CT::T_GROUP_IMPORT_BRACE_OPEN, '{']); $tokens[$closeIndex] = new Token([CT::T_GROUP_IMPORT_BRACE_CLOSE, '}']); } } isComment()) { return; } $content = $token->getContent(); $trimmedContent = rtrim($content); if ($content === $trimmedContent) { return; } $whitespaces = substr($content, \strlen($trimmedContent)); $tokens[$index] = new Token([$token->getId(), $trimmedContent]); if (isset($tokens[$index + 1]) && $tokens[$index + 1]->isWhitespace()) { $tokens[$index + 1] = new Token([T_WHITESPACE, $whitespaces.$tokens[$index + 1]->getContent()]); } else { $tokens->insertAt($index + 1, new Token([T_WHITESPACE, $whitespaces])); } } } equals(':')) { return; } $endIndex = $tokens->getPrevMeaningfulToken($index); if (!$tokens[$endIndex]->equals(')')) { return; } $startIndex = $tokens->findBlockStart(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $endIndex); $prevIndex = $tokens->getPrevMeaningfulToken($startIndex); $prevToken = $tokens[$prevIndex]; if ($prevToken->isGivenKind(T_STRING)) { $prevIndex = $tokens->getPrevMeaningfulToken($prevIndex); $prevToken = $tokens[$prevIndex]; } $prevKinds = [T_FUNCTION, CT::T_RETURN_REF, CT::T_USE_LAMBDA]; if (\PHP_VERSION_ID >= 70400) { $prevKinds[] = T_FN; } if ($prevToken->isGivenKind($prevKinds)) { $tokens[$index] = new Token([CT::T_TYPE_COLON, ':']); } } } equals('?')) { return; } $prevIndex = $tokens->getPrevMeaningfulToken($index); $prevToken = $tokens[$prevIndex]; if ($prevToken->equalsAny(['(', ',', [CT::T_TYPE_COLON], [T_PRIVATE], [T_PROTECTED], [T_PUBLIC], [T_VAR], [T_STATIC]])) { $tokens[$index] = new Token([CT::T_NULLABLE_TYPE, '?']); } } } tokens = $tokens; } public function getClassyElements() { $this->tokens->rewind(); $elements = []; for ($index = 1, $count = \count($this->tokens) - 2; $index < $count; ++$index) { if ($this->tokens[$index]->isClassy()) { list($index, $newElements) = $this->findClassyElements($index, $index); $elements += $newElements; } } ksort($elements); return $elements; } public function getImportUseIndexes($perNamespace = false) { $tokens = $this->tokens; $tokens->rewind(); $uses = []; $namespaceIndex = 0; for ($index = 0, $limit = $tokens->count(); $index < $limit; ++$index) { $token = $tokens[$index]; if ($token->isGivenKind(T_NAMESPACE)) { $nextTokenIndex = $tokens->getNextTokenOfKind($index, [';', '{']); $nextToken = $tokens[$nextTokenIndex]; if ($nextToken->equals('{')) { $index = $nextTokenIndex; } if ($perNamespace) { ++$namespaceIndex; } continue; } if ($token->isGivenKind(T_USE)) { $uses[$namespaceIndex][] = $index; } } if (!$perNamespace && isset($uses[$namespaceIndex])) { return $uses[$namespaceIndex]; } return $uses; } public function isArray($index) { return $this->tokens[$index]->isGivenKind([T_ARRAY, CT::T_ARRAY_SQUARE_BRACE_OPEN]); } public function isArrayMultiLine($index) { if (!$this->isArray($index)) { throw new \InvalidArgumentException(sprintf('Not an array at given index %d.', $index)); } $tokens = $this->tokens; if ($tokens[$index]->isGivenKind(T_ARRAY)) { $index = $tokens->getNextMeaningfulToken($index); } $endIndex = $tokens[$index]->equals('(') ? $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index) : $tokens->findBlockEnd(Tokens::BLOCK_TYPE_ARRAY_SQUARE_BRACE, $index) ; for (++$index; $index < $endIndex; ++$index) { $token = $tokens[$index]; $blockType = Tokens::detectBlockType($token); if ($blockType && $blockType['isStart']) { $index = $tokens->findBlockEnd($blockType['type'], $index); continue; } if ( $token->isWhitespace() && !$tokens[$index - 1]->isGivenKind(T_END_HEREDOC) && false !== strpos($token->getContent(), "\n") ) { return true; } } return false; } public function getMethodAttributes($index) { $tokens = $this->tokens; $token = $tokens[$index]; if (!$token->isGivenKind(T_FUNCTION)) { throw new \LogicException(sprintf('No T_FUNCTION at given index %d, got %s.', $index, $token->getName())); } $attributes = [ 'visibility' => null, 'static' => false, 'abstract' => false, 'final' => false, ]; for ($i = $index; $i >= 0; --$i) { $tokenIndex = $tokens->getPrevMeaningfulToken($i); $i = $tokenIndex; $token = $tokens[$tokenIndex]; if ($token->isGivenKind(T_STATIC)) { $attributes['static'] = true; continue; } if ($token->isGivenKind(T_FINAL)) { $attributes['final'] = true; continue; } if ($token->isGivenKind(T_ABSTRACT)) { $attributes['abstract'] = true; continue; } if ($token->isGivenKind(T_PRIVATE)) { $attributes['visibility'] = T_PRIVATE; continue; } if ($token->isGivenKind(T_PROTECTED)) { $attributes['visibility'] = T_PROTECTED; continue; } if ($token->isGivenKind(T_PUBLIC)) { $attributes['visibility'] = T_PUBLIC; continue; } break; } return $attributes; } public function isAnonymousClass($index) { $tokens = $this->tokens; $token = $tokens[$index]; if (!$token->isClassy()) { throw new \LogicException(sprintf('No classy token at given index %d.', $index)); } if (!$token->isGivenKind(T_CLASS)) { return false; } return $tokens[$tokens->getPrevMeaningfulToken($index)]->isGivenKind(T_NEW); } public function isLambda($index) { if ( !$this->tokens[$index]->isGivenKind(T_FUNCTION) && (\PHP_VERSION_ID < 70400 || !$this->tokens[$index]->isGivenKind(T_FN)) ) { throw new \LogicException(sprintf('No T_FUNCTION or T_FN at given index %d, got %s.', $index, $this->tokens[$index]->getName())); } $startParenthesisIndex = $this->tokens->getNextMeaningfulToken($index); $startParenthesisToken = $this->tokens[$startParenthesisIndex]; if ($startParenthesisToken->isGivenKind(CT::T_RETURN_REF)) { $startParenthesisIndex = $this->tokens->getNextMeaningfulToken($startParenthesisIndex); $startParenthesisToken = $this->tokens[$startParenthesisIndex]; } return $startParenthesisToken->equals('('); } public function isConstantInvocation($index) { if (!$this->tokens[$index]->isGivenKind(T_STRING)) { throw new \LogicException(sprintf('No T_STRING at given index %d, got %s.', $index, $this->tokens[$index]->getName())); } $nextIndex = $this->tokens->getNextMeaningfulToken($index); if ( $this->tokens[$nextIndex]->equalsAny(['(', '{']) || $this->tokens[$nextIndex]->isGivenKind([T_AS, T_DOUBLE_COLON, T_ELLIPSIS, T_NS_SEPARATOR, CT::T_RETURN_REF, CT::T_TYPE_ALTERNATION, T_VARIABLE]) ) { return false; } $prevIndex = $this->tokens->getPrevMeaningfulToken($index); if ($this->tokens[$prevIndex]->isGivenKind([T_AS, T_CLASS, T_CONST, T_DOUBLE_COLON, T_FUNCTION, T_GOTO, CT::T_GROUP_IMPORT_BRACE_OPEN, T_INTERFACE, T_OBJECT_OPERATOR, T_TRAIT, CT::T_TYPE_COLON])) { return false; } while ($this->tokens[$prevIndex]->isGivenKind([CT::T_NAMESPACE_OPERATOR, T_NS_SEPARATOR, T_STRING])) { $prevIndex = $this->tokens->getPrevMeaningfulToken($prevIndex); } if ($this->tokens[$prevIndex]->isGivenKind([CT::T_CONST_IMPORT, T_EXTENDS, CT::T_FUNCTION_IMPORT, T_IMPLEMENTS, T_INSTANCEOF, T_INSTEADOF, T_NAMESPACE, T_NEW, CT::T_NULLABLE_TYPE, CT::T_TYPE_COLON, T_USE, CT::T_USE_TRAIT])) { return false; } if ($this->tokens[$nextIndex]->equals('&') && $this->tokens[$this->tokens->getNextMeaningfulToken($nextIndex)]->isGivenKind(T_VARIABLE)) { $checkIndex = $this->tokens->getPrevTokenOfKind($prevIndex, [';', '{', '}', [T_FUNCTION], [T_OPEN_TAG], [T_OPEN_TAG_WITH_ECHO]]); if ($this->tokens[$checkIndex]->isGivenKind(T_FUNCTION)) { return false; } } if ($this->tokens[$prevIndex]->equals(',')) { $checkIndex = $prevIndex; while ($this->tokens[$checkIndex]->equalsAny([',', [T_AS], [CT::T_NAMESPACE_OPERATOR], [T_NS_SEPARATOR], [T_STRING]])) { $checkIndex = $this->tokens->getPrevMeaningfulToken($checkIndex); } if ($this->tokens[$checkIndex]->isGivenKind([T_EXTENDS, CT::T_GROUP_IMPORT_BRACE_OPEN, T_IMPLEMENTS, T_USE, CT::T_USE_TRAIT])) { return false; } } if ($this->tokens[$prevIndex]->equals('[') && $this->tokens[$nextIndex]->equals(']')) { $checkToken = $this->tokens[$this->tokens->getNextMeaningfulToken($nextIndex)]; if ($checkToken->equals('"') || $checkToken->isGivenKind([T_CURLY_OPEN, T_DOLLAR_OPEN_CURLY_BRACES, T_ENCAPSED_AND_WHITESPACE, T_VARIABLE])) { return false; } } if ($this->tokens[$nextIndex]->equals(':') && $this->tokens[$prevIndex]->equalsAny([';', '}', [T_OPEN_TAG], [T_OPEN_TAG_WITH_ECHO]])) { return false; } return true; } public function isUnarySuccessorOperator($index) { static $allowedPrevToken = [ ']', [T_STRING], [T_VARIABLE], [CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE], [CT::T_DYNAMIC_PROP_BRACE_CLOSE], [CT::T_DYNAMIC_VAR_BRACE_CLOSE], ]; $tokens = $this->tokens; $token = $tokens[$index]; if (!$token->isGivenKind([T_INC, T_DEC])) { return false; } $prevToken = $tokens[$tokens->getPrevMeaningfulToken($index)]; return $prevToken->equalsAny($allowedPrevToken); } public function isUnaryPredecessorOperator($index) { static $potentialSuccessorOperator = [T_INC, T_DEC]; static $potentialBinaryOperator = ['+', '-', '&', [CT::T_RETURN_REF]]; static $otherOperators; if (null === $otherOperators) { $otherOperators = ['!', '~', '@', [T_ELLIPSIS]]; } static $disallowedPrevTokens; if (null === $disallowedPrevTokens) { $disallowedPrevTokens = [ ']', '}', ')', '"', '`', [CT::T_ARRAY_SQUARE_BRACE_CLOSE], [CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE], [CT::T_DYNAMIC_PROP_BRACE_CLOSE], [CT::T_DYNAMIC_VAR_BRACE_CLOSE], [T_CLASS_C], [T_CONSTANT_ENCAPSED_STRING], [T_DEC], [T_DIR], [T_DNUMBER], [T_FILE], [T_FUNC_C], [T_INC], [T_LINE], [T_LNUMBER], [T_METHOD_C], [T_NS_C], [T_STRING], [T_TRAIT_C], [T_VARIABLE], ]; } $tokens = $this->tokens; $token = $tokens[$index]; if ($token->isGivenKind($potentialSuccessorOperator)) { return !$this->isUnarySuccessorOperator($index); } if ($token->equalsAny($otherOperators)) { return true; } if (!$token->equalsAny($potentialBinaryOperator)) { return false; } $prevToken = $tokens[$tokens->getPrevMeaningfulToken($index)]; if (!$prevToken->equalsAny($disallowedPrevTokens)) { return true; } if (!$token->equals('&') || !$prevToken->isGivenKind(T_STRING)) { return false; } static $searchTokens = [ ';', '{', '}', [T_FUNCTION], [T_OPEN_TAG], [T_OPEN_TAG_WITH_ECHO], ]; $prevToken = $tokens[$tokens->getPrevTokenOfKind($index, $searchTokens)]; return $prevToken->isGivenKind(T_FUNCTION); } public function isBinaryOperator($index) { static $nonArrayOperators = [ '=' => true, '*' => true, '/' => true, '%' => true, '<' => true, '>' => true, '|' => true, '^' => true, '.' => true, ]; static $potentialUnaryNonArrayOperators = [ '+' => true, '-' => true, '&' => true, ]; static $arrayOperators; if (null === $arrayOperators) { $arrayOperators = [ T_AND_EQUAL => true, T_BOOLEAN_AND => true, T_BOOLEAN_OR => true, T_CONCAT_EQUAL => true, T_DIV_EQUAL => true, T_DOUBLE_ARROW => true, T_IS_EQUAL => true, T_IS_GREATER_OR_EQUAL => true, T_IS_IDENTICAL => true, T_IS_NOT_EQUAL => true, T_IS_NOT_IDENTICAL => true, T_IS_SMALLER_OR_EQUAL => true, T_LOGICAL_AND => true, T_LOGICAL_OR => true, T_LOGICAL_XOR => true, T_MINUS_EQUAL => true, T_MOD_EQUAL => true, T_MUL_EQUAL => true, T_OR_EQUAL => true, T_PLUS_EQUAL => true, T_POW => true, T_POW_EQUAL => true, T_SL => true, T_SL_EQUAL => true, T_SR => true, T_SR_EQUAL => true, T_XOR_EQUAL => true, CT::T_TYPE_ALTERNATION => true, ]; if (\defined('T_SPACESHIP')) { $arrayOperators[T_SPACESHIP] = true; } if (\defined('T_COALESCE')) { $arrayOperators[T_COALESCE] = true; } if (\defined('T_COALESCE_EQUAL')) { $arrayOperators[T_COALESCE_EQUAL] = true; } } $tokens = $this->tokens; $token = $tokens[$index]; if ($token->isArray()) { return isset($arrayOperators[$token->getId()]); } if (isset($nonArrayOperators[$token->getContent()])) { return true; } if (isset($potentialUnaryNonArrayOperators[$token->getContent()])) { return !$this->isUnaryPredecessorOperator($index); } return false; } public function isWhilePartOfDoWhile($index) { $tokens = $this->tokens; $token = $tokens[$index]; if (!$token->isGivenKind(T_WHILE)) { throw new \LogicException(sprintf('No T_WHILE at given index %d, got %s.', $index, $token->getName())); } $endIndex = $tokens->getPrevMeaningfulToken($index); if (!$tokens[$endIndex]->equals('}')) { return false; } $startIndex = $tokens->findBlockStart(Tokens::BLOCK_TYPE_CURLY_BRACE, $endIndex); $beforeStartIndex = $tokens->getPrevMeaningfulToken($startIndex); return $tokens[$beforeStartIndex]->isGivenKind(T_DO); } private function findClassyElements($classIndex, $index) { $elements = []; $curlyBracesLevel = 0; $bracesLevel = 0; ++$index; for ($count = \count($this->tokens); $index < $count; ++$index) { $token = $this->tokens[$index]; if ($token->isGivenKind(T_ENCAPSED_AND_WHITESPACE)) { continue; } if ($token->isClassy()) { $nestedClassIndex = $index; $index = $this->tokens->getNextMeaningfulToken($index); if ($this->tokens[$index]->equals('(')) { ++$index; for ($nestedBracesLevel = 1; $index < $count; ++$index) { $token = $this->tokens[$index]; if ($token->equals('(')) { ++$nestedBracesLevel; continue; } if ($token->equals(')')) { --$nestedBracesLevel; if (0 === $nestedBracesLevel) { list($index, $newElements) = $this->findClassyElements($nestedClassIndex, $index); $elements += $newElements; break; } continue; } if ($token->isClassy()) { list($index, $newElements) = $this->findClassyElements($index, $index); $elements += $newElements; } } } else { list($index, $newElements) = $this->findClassyElements($nestedClassIndex, $nestedClassIndex); $elements += $newElements; } continue; } if ($token->equals('(')) { ++$bracesLevel; continue; } if ($token->equals(')')) { --$bracesLevel; continue; } if ($token->equals('{')) { ++$curlyBracesLevel; continue; } if ($token->equals('}')) { --$curlyBracesLevel; if (0 === $curlyBracesLevel) { break; } continue; } if (1 !== $curlyBracesLevel || !$token->isArray()) { continue; } if (0 === $bracesLevel && $token->isGivenKind(T_VARIABLE)) { $elements[$index] = [ 'token' => $token, 'type' => 'property', 'classIndex' => $classIndex, ]; continue; } if ($token->isGivenKind(T_FUNCTION)) { $elements[$index] = [ 'token' => $token, 'type' => 'method', 'classIndex' => $classIndex, ]; } elseif ($token->isGivenKind(T_CONST)) { $elements[$index] = [ 'token' => $token, 'type' => 'const', 'classIndex' => $classIndex, ]; } } return [$index, $elements]; } } $val) { $this[$key] = clone $val; } } public static function isLegacyMode() { return self::$isLegacyMode; } public static function setLegacyMode($isLegacy) { if (getenv('PHP_CS_FIXER_FUTURE_MODE') && $isLegacy) { throw new \RuntimeException('Cannot enable `legacy mode` when using `future mode`. This check was performed as `PHP_CS_FIXER_FUTURE_MODE` env var is set.'); } self::$isLegacyMode = $isLegacy; } public static function clearCache($key = null) { if (null === $key) { self::$cache = []; return; } if (self::hasCache($key)) { unset(self::$cache[$key]); } } public static function detectBlockType(Token $token) { foreach (self::getBlockEdgeDefinitions() as $type => $definition) { if ($token->equals($definition['start'])) { return ['type' => $type, 'isStart' => true]; } if ($token->equals($definition['end'])) { return ['type' => $type, 'isStart' => false]; } } return null; } public static function fromArray($array, $saveIndexes = null) { $tokens = new self(\count($array)); if (null === $saveIndexes || $saveIndexes) { foreach ($array as $key => $val) { $tokens[$key] = $val; } } else { $index = 0; foreach ($array as $val) { $tokens[$index++] = $val; } } $tokens->generateCode(); return $tokens; } public static function fromCode($code) { $codeHash = self::calculateCodeHash($code); if (self::hasCache($codeHash)) { $tokens = self::getCache($codeHash); $tokens->generateCode(); if ($codeHash === $tokens->codeHash) { $tokens->clearEmptyTokens(); $tokens->clearChanged(); return $tokens; } } $tokens = new self(); $tokens->setCode($code); $tokens->clearChanged(); return $tokens; } public static function getBlockEdgeDefinitions() { return [ self::BLOCK_TYPE_CURLY_BRACE => [ 'start' => '{', 'end' => '}', ], self::BLOCK_TYPE_PARENTHESIS_BRACE => [ 'start' => '(', 'end' => ')', ], self::BLOCK_TYPE_INDEX_SQUARE_BRACE => [ 'start' => '[', 'end' => ']', ], self::BLOCK_TYPE_ARRAY_SQUARE_BRACE => [ 'start' => [CT::T_ARRAY_SQUARE_BRACE_OPEN, '['], 'end' => [CT::T_ARRAY_SQUARE_BRACE_CLOSE, ']'], ], self::BLOCK_TYPE_DYNAMIC_PROP_BRACE => [ 'start' => [CT::T_DYNAMIC_PROP_BRACE_OPEN, '{'], 'end' => [CT::T_DYNAMIC_PROP_BRACE_CLOSE, '}'], ], self::BLOCK_TYPE_DYNAMIC_VAR_BRACE => [ 'start' => [CT::T_DYNAMIC_VAR_BRACE_OPEN, '{'], 'end' => [CT::T_DYNAMIC_VAR_BRACE_CLOSE, '}'], ], self::BLOCK_TYPE_ARRAY_INDEX_CURLY_BRACE => [ 'start' => [CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN, '{'], 'end' => [CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE, '}'], ], self::BLOCK_TYPE_GROUP_IMPORT_BRACE => [ 'start' => [CT::T_GROUP_IMPORT_BRACE_OPEN, '{'], 'end' => [CT::T_GROUP_IMPORT_BRACE_CLOSE, '}'], ], self::BLOCK_TYPE_DESTRUCTURING_SQUARE_BRACE => [ 'start' => [CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN, '['], 'end' => [CT::T_DESTRUCTURING_SQUARE_BRACE_CLOSE, ']'], ], self::BLOCK_TYPE_BRACE_CLASS_INSTANTIATION => [ 'start' => [CT::T_BRACE_CLASS_INSTANTIATION_OPEN, '('], 'end' => [CT::T_BRACE_CLASS_INSTANTIATION_CLOSE, ')'], ], ]; } public function setSize($size) { if ($this->getSize() !== $size) { $this->changed = true; parent::setSize($size); } } public function offsetUnset($index) { $this->changed = true; $this->unregisterFoundToken($this[$index]); parent::offsetUnset($index); } public function offsetSet($index, $newval) { $this->blockEndCache = []; if (!isset($this[$index]) || !$this[$index]->equals($newval)) { $this->changed = true; if (isset($this[$index])) { $this->unregisterFoundToken($this[$index]); } $this->registerFoundToken($newval); } parent::offsetSet($index, $newval); } public function clearChanged() { $this->changed = false; if (self::isLegacyMode()) { foreach ($this as $token) { $token->clearChanged(); } } } public function clearEmptyTokens() { $limit = $this->count(); $index = 0; for (; $index < $limit; ++$index) { if ($this->isEmptyAt($index)) { break; } } if ($limit === $index) { return; } for ($count = $index; $index < $limit; ++$index) { if (!$this->isEmptyAt($index)) { $token = $this[$index]; $this[$count++] = $token; } } $this->setSize($count); } public function ensureWhitespaceAtIndex($index, $indexOffset, $whitespace) { $removeLastCommentLine = static function (self $tokens, $index, $indexOffset, $whitespace) { $token = $tokens[$index]; if (1 === $indexOffset && $token->isGivenKind(T_OPEN_TAG)) { if (0 === strpos($whitespace, "\r\n")) { $tokens[$index] = new Token([T_OPEN_TAG, rtrim($token->getContent())."\r\n"]); return \strlen($whitespace) > 2 ? substr($whitespace, 2) : '' ; } $tokens[$index] = new Token([T_OPEN_TAG, rtrim($token->getContent()).$whitespace[0]]); return \strlen($whitespace) > 1 ? substr($whitespace, 1) : '' ; } return $whitespace; }; if ($this[$index]->isWhitespace()) { $whitespace = $removeLastCommentLine($this, $index - 1, $indexOffset, $whitespace); if ('' === $whitespace) { $this->clearAt($index); } else { $this[$index] = new Token([T_WHITESPACE, $whitespace]); } return false; } $whitespace = $removeLastCommentLine($this, $index, $indexOffset, $whitespace); if ('' === $whitespace) { return false; } $this->insertAt( $index + $indexOffset, [ new Token([T_WHITESPACE, $whitespace]), ] ); return true; } public function findBlockEnd($type, $searchIndex, $findEnd = true) { if (3 === \func_num_args()) { if ($findEnd) { @trigger_error('Argument #3 of Tokens::findBlockEnd is deprecated and will be removed in 3.0, you can safely drop the argument.', E_USER_DEPRECATED); } else { @trigger_error('Argument #3 of Tokens::findBlockEnd is deprecated and will be removed in 3.0, use Tokens::findBlockStart instead.', E_USER_DEPRECATED); } } return $this->findOppositeBlockEdge($type, $searchIndex, $findEnd); } public function findBlockStart($type, $searchIndex) { return $this->findOppositeBlockEdge($type, $searchIndex, false); } public function findGivenKind($possibleKind, $start = 0, $end = null) { $this->rewind(); if (null === $end) { $end = $this->count(); } $elements = []; $possibleKinds = (array) $possibleKind; foreach ($possibleKinds as $kind) { $elements[$kind] = []; } if (!self::isLegacyMode()) { $possibleKinds = array_filter($possibleKinds, function ($kind) { return $this->isTokenKindFound($kind); }); } if (\count($possibleKinds)) { for ($i = $start; $i < $end; ++$i) { $token = $this[$i]; if ($token->isGivenKind($possibleKinds)) { $elements[$token->getId()][$i] = $token; } } } return \is_array($possibleKind) ? $elements : $elements[$possibleKind]; } public function generateCode() { $code = $this->generatePartialCode(0, \count($this) - 1); $this->changeCodeHash(self::calculateCodeHash($code)); return $code; } public function generatePartialCode($start, $end) { $code = ''; for ($i = $start; $i <= $end; ++$i) { $code .= $this[$i]->getContent(); } return $code; } public function getCodeHash() { return $this->codeHash; } public function getNextNonWhitespace($index, $whitespaces = null) { return $this->getNonWhitespaceSibling($index, 1, $whitespaces); } public function getNextTokenOfKind($index, array $tokens = [], $caseSensitive = true) { return $this->getTokenOfKindSibling($index, 1, $tokens, $caseSensitive); } public function getNonWhitespaceSibling($index, $direction, $whitespaces = null) { while (true) { $index += $direction; if (!$this->offsetExists($index)) { return null; } $token = $this[$index]; if (!$token->isWhitespace($whitespaces)) { return $index; } } } public function getPrevNonWhitespace($index, $whitespaces = null) { return $this->getNonWhitespaceSibling($index, -1, $whitespaces); } public function getPrevTokenOfKind($index, array $tokens = [], $caseSensitive = true) { return $this->getTokenOfKindSibling($index, -1, $tokens, $caseSensitive); } public function getTokenOfKindSibling($index, $direction, array $tokens = [], $caseSensitive = true) { if (!self::isLegacyMode()) { $tokens = array_filter($tokens, function ($token) { return $this->isTokenKindFound($this->extractTokenKind($token)); }); } if (!\count($tokens)) { return null; } while (true) { $index += $direction; if (!$this->offsetExists($index)) { return null; } $token = $this[$index]; if ($token->equalsAny($tokens, $caseSensitive)) { return $index; } } } public function getTokenNotOfKindSibling($index, $direction, array $tokens = []) { while (true) { $index += $direction; if (!$this->offsetExists($index)) { return null; } if ($this->isEmptyAt($index)) { continue; } if ($this[$index]->equalsAny($tokens)) { continue; } return $index; } } public function getMeaningfulTokenSibling($index, $direction) { return $this->getTokenNotOfKindSibling( $index, $direction, [[T_WHITESPACE], [T_COMMENT], [T_DOC_COMMENT]] ); } public function getNonEmptySibling($index, $direction) { while (true) { $index += $direction; if (!$this->offsetExists($index)) { return null; } if (!$this->isEmptyAt($index)) { return $index; } } } public function getNextMeaningfulToken($index) { return $this->getMeaningfulTokenSibling($index, 1); } public function getPrevMeaningfulToken($index) { return $this->getMeaningfulTokenSibling($index, -1); } public function findSequence(array $sequence, $start = 0, $end = null, $caseSensitive = true) { $sequenceCount = \count($sequence); if (0 === $sequenceCount) { throw new \InvalidArgumentException('Invalid sequence.'); } $end = null === $end ? \count($this) - 1 : min($end, \count($this) - 1); if ($start + $sequenceCount - 1 > $end) { return null; } foreach ($sequence as $key => $token) { if (!$token instanceof Token) { if (\is_array($token) && !isset($token[1])) { $token[1] = 'DUMMY'; } $token = new Token($token); } if ($token->isWhitespace() || $token->isComment() || '' === $token->getContent()) { throw new \InvalidArgumentException(sprintf('Non-meaningful token at position: "%s".', $key)); } } if (!self::isLegacyMode()) { foreach ($sequence as $token) { if (!$this->isTokenKindFound($this->extractTokenKind($token))) { return null; } } } $key = key($sequence); $firstCs = Token::isKeyCaseSensitive($caseSensitive, $key); $firstToken = $sequence[$key]; unset($sequence[$key]); $index = $start - 1; while (null !== $index && $index <= $end) { $index = $this->getNextTokenOfKind($index, [$firstToken], $firstCs); if (null === $index || $index > $end) { return null; } $result = [$index => $this[$index]]; $currIdx = $index; foreach ($sequence as $key => $token) { $currIdx = $this->getNextMeaningfulToken($currIdx); if (null === $currIdx || $currIdx > $end) { return null; } if (!$this[$currIdx]->equals($token, Token::isKeyCaseSensitive($caseSensitive, $key))) { continue 2; } $result[$currIdx] = $this[$currIdx]; } if (\count($sequence) < \count($result)) { return $result; } } return null; } public function insertAt($index, $items) { $items = \is_array($items) || $items instanceof self ? $items : [$items]; $itemsCnt = \count($items); if (0 === $itemsCnt) { return; } $oldSize = \count($this); $this->changed = true; $this->blockEndCache = []; $this->setSize($oldSize + $itemsCnt); for ($i = $oldSize + $itemsCnt - 1; $i >= $index; --$i) { $oldItem = parent::offsetExists($i - $itemsCnt) ? parent::offsetGet($i - $itemsCnt) : new Token(''); parent::offsetSet($i, $oldItem); } for ($i = 0; $i < $itemsCnt; ++$i) { if ('' === $items[$i]->getContent()) { throw new \InvalidArgumentException('Must not add empty token to collection.'); } $this->registerFoundToken($items[$i]); parent::offsetSet($i + $index, $items[$i]); } } public function isChanged() { if ($this->changed) { return true; } if (self::isLegacyMode()) { foreach ($this as $token) { if ($token->isChanged()) { return true; } } } return false; } public function isEmptyAt($index) { $token = $this[$index]; return null === $token->getId() && '' === $token->getContent(); } public function clearAt($index) { $this[$index] = new Token(''); } public function overrideAt($index, $token) { @trigger_error(__METHOD__.' is deprecated and will be removed in 3.0, use offsetSet instead.', E_USER_DEPRECATED); self::$isLegacyMode = true; $this[$index]->override($token); $this->registerFoundToken($token); } public function overrideRange($indexStart, $indexEnd, $items) { $oldCode = $this->generatePartialCode($indexStart, $indexEnd); $newCode = ''; foreach ($items as $item) { $newCode .= $item->getContent(); } if ($oldCode === $newCode) { return; } $indexToChange = $indexEnd - $indexStart + 1; $itemsCount = \count($items); if ($itemsCount > $indexToChange) { $placeholders = []; while ($itemsCount > $indexToChange) { $placeholders[] = new Token('__PLACEHOLDER__'); ++$indexToChange; } $this->insertAt($indexEnd + 1, $placeholders); } foreach ($items as $itemIndex => $item) { $this[$indexStart + $itemIndex] = $item; } if ($itemsCount < $indexToChange) { $this->clearRange($indexStart + $itemsCount, $indexEnd); } } public function removeLeadingWhitespace($index, $whitespaces = null) { $this->removeWhitespaceSafely($index, -1, $whitespaces); } public function removeTrailingWhitespace($index, $whitespaces = null) { $this->removeWhitespaceSafely($index, 1, $whitespaces); } public function setCode($code) { if ($code === $this->generateCode()) { return; } $this->setSize(0); $tokens = \defined('TOKEN_PARSE') ? token_get_all($code, TOKEN_PARSE) : token_get_all($code); $this->setSize(\count($tokens)); foreach ($tokens as $index => $token) { $this[$index] = new Token($token); } $transformers = Transformers::create(); $transformers->transform($this); $this->foundTokenKinds = []; foreach ($this as $token) { $this->registerFoundToken($token); } $this->rewind(); $this->changeCodeHash(self::calculateCodeHash($code)); $this->changed = true; } public function toJson() { static $options = null; if (null === $options) { $options = Utils::calculateBitmask(['JSON_PRETTY_PRINT', 'JSON_NUMERIC_CHECK']); } $output = new \SplFixedArray(\count($this)); foreach ($this as $index => $token) { $output[$index] = $token->toArray(); } $this->rewind(); return json_encode($output, $options); } public function isAllTokenKindsFound(array $tokenKinds) { foreach ($tokenKinds as $tokenKind) { if (empty($this->foundTokenKinds[$tokenKind])) { return false; } } return true; } public function isAnyTokenKindsFound(array $tokenKinds) { foreach ($tokenKinds as $tokenKind) { if (!empty($this->foundTokenKinds[$tokenKind])) { return true; } } return false; } public function isTokenKindFound($tokenKind) { return !empty($this->foundTokenKinds[$tokenKind]); } public function countTokenKind($tokenKind) { if (self::isLegacyMode()) { throw new \RuntimeException(sprintf('"%s" is not available in legacy mode.', __METHOD__)); } return isset($this->foundTokenKinds[$tokenKind]) ? $this->foundTokenKinds[$tokenKind] : 0; } public function clearRange($indexStart, $indexEnd) { for ($i = $indexStart; $i <= $indexEnd; ++$i) { $this->clearAt($i); } } public function isMonolithicPhp() { $size = $this->count(); if (0 === $size) { return false; } if (self::isLegacyMode()) { if ($this[0]->isGivenKind(T_INLINE_HTML) || $this[$size - 1]->isGivenKind(T_INLINE_HTML)) { return false; } for ($index = 1; $index < $size; ++$index) { if ($this[$index]->isGivenKind([T_INLINE_HTML, T_OPEN_TAG, T_OPEN_TAG_WITH_ECHO])) { return false; } } return true; } if ($this->isTokenKindFound(T_INLINE_HTML)) { return false; } return 1 >= ($this->countTokenKind(T_OPEN_TAG) + $this->countTokenKind(T_OPEN_TAG_WITH_ECHO)); } public function isPartialCodeMultiline($start, $end) { for ($i = $start; $i <= $end; ++$i) { if (false !== strpos($this[$i]->getContent(), "\n")) { return true; } } return false; } public function clearTokenAndMergeSurroundingWhitespace($index) { $count = \count($this); $this->clearAt($index); if ($index === $count - 1) { return; } $nextIndex = $this->getNonEmptySibling($index, 1); if (null === $nextIndex || !$this[$nextIndex]->isWhitespace()) { return; } $prevIndex = $this->getNonEmptySibling($index, -1); if ($this[$prevIndex]->isWhitespace()) { $this[$prevIndex] = new Token([T_WHITESPACE, $this[$prevIndex]->getContent().$this[$nextIndex]->getContent()]); } elseif ($this->isEmptyAt($prevIndex + 1)) { $this[$prevIndex + 1] = new Token([T_WHITESPACE, $this[$nextIndex]->getContent()]); } $this->clearAt($nextIndex); } private function removeWhitespaceSafely($index, $direction, $whitespaces = null) { $whitespaceIndex = $this->getNonEmptySibling($index, $direction); if (isset($this[$whitespaceIndex]) && $this[$whitespaceIndex]->isWhitespace()) { $newContent = ''; $tokenToCheck = $this[$whitespaceIndex]; if (isset($this[$whitespaceIndex - 1]) && $this[$whitespaceIndex - 1]->isComment() && '/*' !== substr($this[$whitespaceIndex - 1]->getContent(), 0, 2)) { list($emptyString, $newContent, $whitespacesToCheck) = Preg::split('/^(\R)/', $this[$whitespaceIndex]->getContent(), -1, PREG_SPLIT_DELIM_CAPTURE); if ('' === $whitespacesToCheck) { return; } $tokenToCheck = new Token([T_WHITESPACE, $whitespacesToCheck]); } if (!$tokenToCheck->isWhitespace($whitespaces)) { return; } if ('' === $newContent) { $this->clearAt($whitespaceIndex); } else { $this[$whitespaceIndex] = new Token([T_WHITESPACE, $newContent]); } } } private function findOppositeBlockEdge($type, $searchIndex, $findEnd) { $blockEdgeDefinitions = self::getBlockEdgeDefinitions(); if (!isset($blockEdgeDefinitions[$type])) { throw new \InvalidArgumentException(sprintf('Invalid param type: "%s".', $type)); } if (!self::isLegacyMode() && isset($this->blockEndCache[$searchIndex])) { return $this->blockEndCache[$searchIndex]; } $startEdge = $blockEdgeDefinitions[$type]['start']; $endEdge = $blockEdgeDefinitions[$type]['end']; $startIndex = $searchIndex; $endIndex = $this->count() - 1; $indexOffset = 1; if (!$findEnd) { list($startEdge, $endEdge) = [$endEdge, $startEdge]; $indexOffset = -1; $endIndex = 0; } if (!$this[$startIndex]->equals($startEdge)) { throw new \InvalidArgumentException(sprintf('Invalid param $startIndex - not a proper block "%s".', $findEnd ? 'start' : 'end')); } $blockLevel = 0; for ($index = $startIndex; $index !== $endIndex; $index += $indexOffset) { $token = $this[$index]; if ($token->equals($startEdge)) { ++$blockLevel; continue; } if ($token->equals($endEdge)) { --$blockLevel; if (0 === $blockLevel) { break; } continue; } } if (!$this[$index]->equals($endEdge)) { throw new \UnexpectedValueException(sprintf('Missing block "%s".', $findEnd ? 'end' : 'start')); } $this->blockEndCache[$startIndex] = $index; $this->blockEndCache[$index] = $startIndex; return $index; } private static function calculateCodeHash($code) { return CodeHasher::calculateCodeHash($code); } private static function getCache($key) { if (!self::hasCache($key)) { throw new \OutOfBoundsException(sprintf('Unknown cache key: "%s".', $key)); } return self::$cache[$key]; } private static function hasCache($key) { return isset(self::$cache[$key]); } private static function setCache($key, self $value) { self::$cache[$key] = $value; } private function changeCodeHash($codeHash) { if (null !== $this->codeHash) { self::clearCache($this->codeHash); } $this->codeHash = $codeHash; self::setCache($this->codeHash, $this); } private function registerFoundToken($token) { $tokenKind = $token instanceof Token ? ($token->isArray() ? $token->getId() : $token->getContent()) : (\is_array($token) ? $token[0] : $token) ; if (!isset($this->foundTokenKinds[$tokenKind])) { $this->foundTokenKinds[$tokenKind] = 0; } ++$this->foundTokenKinds[$tokenKind]; } private function unregisterFoundToken($token) { $tokenKind = $token instanceof Token ? ($token->isArray() ? $token->getId() : $token->getContent()) : (\is_array($token) ? $token[0] : $token) ; if (!isset($this->foundTokenKinds[$tokenKind])) { return; } --$this->foundTokenKinds[$tokenKind]; } private function extractTokenKind($token) { return $token instanceof Token ? ($token->isArray() ? $token->getId() : $token->getContent()) : (\is_array($token) ? $token[0] : $token) ; } } $part) { $tokens[] = new Token([T_STRING, $part]); if ($index !== \count($parts) - 1) { $tokens[] = new Token([T_NS_SEPARATOR, '\\']); } } return $tokens; } } isArray = true; $this->id = $token[0]; $this->content = $token[1]; if ($token[0] && '' === $token[1]) { throw new \InvalidArgumentException('Cannot set empty content for id-based Token.'); } } elseif (\is_string($token)) { $this->isArray = false; $this->content = $token; } else { throw new \InvalidArgumentException(sprintf( 'Cannot recognize input value as valid Token prototype, got "%s".', \is_object($token) ? \get_class($token) : \gettype($token) )); } } public static function getCastTokenKinds() { static $castTokens = [T_ARRAY_CAST, T_BOOL_CAST, T_DOUBLE_CAST, T_INT_CAST, T_OBJECT_CAST, T_STRING_CAST, T_UNSET_CAST]; return $castTokens; } public static function getClassyTokenKinds() { static $classTokens = [T_CLASS, T_TRAIT, T_INTERFACE]; return $classTokens; } public function clear() { @trigger_error(__METHOD__.' is deprecated and will be removed in 3.0.', E_USER_DEPRECATED); Tokens::setLegacyMode(true); $this->content = ''; $this->id = null; $this->isArray = false; } public function clearChanged() { @trigger_error(__METHOD__.' is deprecated and will be removed in 3.0.', E_USER_DEPRECATED); Tokens::setLegacyMode(true); $this->changed = false; } public function equals($other, $caseSensitive = true) { if ($other instanceof self) { if (!$other->isArray) { $otherPrototype = $other->content; } else { $otherPrototype = [ $other->id, $other->content, ]; } } else { $otherPrototype = $other; } if ($this->isArray !== \is_array($otherPrototype)) { return false; } if (!$this->isArray) { return $this->content === $otherPrototype; } if ($this->id !== $otherPrototype[0]) { return false; } if (isset($otherPrototype[1])) { if ($caseSensitive) { if ($this->content !== $otherPrototype[1]) { return false; } } elseif (0 !== strcasecmp($this->content, $otherPrototype[1])) { return false; } } unset($otherPrototype[0], $otherPrototype[1]); return empty($otherPrototype); } public function equalsAny(array $others, $caseSensitive = true) { foreach ($others as $other) { if ($this->equals($other, $caseSensitive)) { return true; } } return false; } public static function isKeyCaseSensitive($caseSensitive, $key) { if (\is_array($caseSensitive)) { return isset($caseSensitive[$key]) ? $caseSensitive[$key] : true; } return $caseSensitive; } public function getPrototype() { if (!$this->isArray) { return $this->content; } return [ $this->id, $this->content, ]; } public function getContent() { return $this->content; } public function getId() { return $this->id; } public function getName() { if (null === $this->id) { return null; } return self::getNameForId($this->id); } public static function getNameForId($id) { if (CT::has($id)) { return CT::getName($id); } $name = token_name($id); return 'UNKNOWN' === $name ? null : $name; } public static function getKeywords() { static $keywords = null; if (null === $keywords) { $keywords = self::getTokenKindsForNames(['T_ABSTRACT', 'T_ARRAY', 'T_AS', 'T_BREAK', 'T_CALLABLE', 'T_CASE', 'T_CATCH', 'T_CLASS', 'T_CLONE', 'T_CONST', 'T_CONTINUE', 'T_DECLARE', 'T_DEFAULT', 'T_DO', 'T_ECHO', 'T_ELSE', 'T_ELSEIF', 'T_EMPTY', 'T_ENDDECLARE', 'T_ENDFOR', 'T_ENDFOREACH', 'T_ENDIF', 'T_ENDSWITCH', 'T_ENDWHILE', 'T_EVAL', 'T_EXIT', 'T_EXTENDS', 'T_FINAL', 'T_FINALLY', 'T_FN', 'T_FOR', 'T_FOREACH', 'T_FUNCTION', 'T_GLOBAL', 'T_GOTO', 'T_HALT_COMPILER', 'T_IF', 'T_IMPLEMENTS', 'T_INCLUDE', 'T_INCLUDE_ONCE', 'T_INSTANCEOF', 'T_INSTEADOF', 'T_INTERFACE', 'T_ISSET', 'T_LIST', 'T_LOGICAL_AND', 'T_LOGICAL_OR', 'T_LOGICAL_XOR', 'T_NAMESPACE', 'T_NEW', 'T_PRINT', 'T_PRIVATE', 'T_PROTECTED', 'T_PUBLIC', 'T_REQUIRE', 'T_REQUIRE_ONCE', 'T_RETURN', 'T_STATIC', 'T_SWITCH', 'T_THROW', 'T_TRAIT', 'T_TRY', 'T_UNSET', 'T_USE', 'T_VAR', 'T_WHILE', 'T_YIELD', 'T_YIELD_FROM', ]) + [ CT::T_ARRAY_TYPEHINT => CT::T_ARRAY_TYPEHINT, CT::T_CLASS_CONSTANT => CT::T_CLASS_CONSTANT, CT::T_CONST_IMPORT => CT::T_CONST_IMPORT, CT::T_FUNCTION_IMPORT => CT::T_FUNCTION_IMPORT, CT::T_NAMESPACE_OPERATOR => CT::T_NAMESPACE_OPERATOR, CT::T_USE_TRAIT => CT::T_USE_TRAIT, CT::T_USE_LAMBDA => CT::T_USE_LAMBDA, ]; } return $keywords; } public static function getMagicConstants() { static $magicConstants = null; if (null === $magicConstants) { $magicConstants = self::getTokenKindsForNames(['T_CLASS_C', 'T_DIR', 'T_FILE', 'T_FUNC_C', 'T_LINE', 'T_METHOD_C', 'T_NS_C', 'T_TRAIT_C']); } return $magicConstants; } public function isArray() { return $this->isArray; } public function isCast() { return $this->isGivenKind(self::getCastTokenKinds()); } public function isChanged() { @trigger_error(__METHOD__.' is deprecated and will be removed in 3.0.', E_USER_DEPRECATED); return $this->changed; } public function isClassy() { return $this->isGivenKind(self::getClassyTokenKinds()); } public function isComment() { static $commentTokens = [T_COMMENT, T_DOC_COMMENT]; return $this->isGivenKind($commentTokens); } public function isEmpty() { @trigger_error(__METHOD__.' is deprecated and will be removed in 3.0.', E_USER_DEPRECATED); return null === $this->id && ('' === $this->content || null === $this->content); } public function isGivenKind($possibleKind) { return $this->isArray && (\is_array($possibleKind) ? \in_array($this->id, $possibleKind, true) : $this->id === $possibleKind); } public function isKeyword() { $keywords = static::getKeywords(); return $this->isArray && isset($keywords[$this->id]); } public function isNativeConstant() { static $nativeConstantStrings = ['true', 'false', 'null']; return $this->isArray && \in_array(strtolower($this->content), $nativeConstantStrings, true); } public function isMagicConstant() { $magicConstants = static::getMagicConstants(); return $this->isArray && isset($magicConstants[$this->id]); } public function isWhitespace($whitespaces = " \t\n\r\0\x0B") { if (null === $whitespaces) { $whitespaces = " \t\n\r\0\x0B"; } if ($this->isArray && !$this->isGivenKind(T_WHITESPACE)) { return false; } return '' === trim($this->content, $whitespaces); } public function override($other) { @trigger_error(__METHOD__.' is deprecated and will be removed in 3.0.', E_USER_DEPRECATED); Tokens::setLegacyMode(true); $prototype = $other instanceof self ? $other->getPrototype() : $other; if ($this->equals($prototype)) { return; } $this->changed = true; if (\is_array($prototype)) { $this->isArray = true; $this->id = $prototype[0]; $this->content = $prototype[1]; return; } $this->isArray = false; $this->id = null; $this->content = $prototype; } public function setContent($content) { @trigger_error(__METHOD__.' is deprecated and will be removed in 3.0.', E_USER_DEPRECATED); Tokens::setLegacyMode(true); if ($this->content === $content) { return; } $this->changed = true; $this->content = $content; if ('' === $content) { @trigger_error(__METHOD__.' shall not be used to clear token, use Tokens::clearAt instead.', E_USER_DEPRECATED); $this->id = null; $this->isArray = false; } } public function toArray() { return [ 'id' => $this->id, 'name' => $this->getName(), 'content' => $this->content, 'isArray' => $this->isArray, 'changed' => $this->changed, ]; } public function toJson(array $options = null) { static $defaultOptions = null; if (null === $options) { if (null === $defaultOptions) { $defaultOptions = Utils::calculateBitmask(['JSON_PRETTY_PRINT', 'JSON_NUMERIC_CHECK']); } $options = $defaultOptions; } else { $options = Utils::calculateBitmask($options); } return json_encode($this->toArray(), $options); } private static function getTokenKindsForNames(array $tokenNames) { $keywords = []; foreach ($tokenNames as $keywordName) { if (\defined($keywordName)) { $keyword = \constant($keywordName); $keywords[$keyword] = $keyword; } } return $keywords; } } tags = Annotation::getTagsWithTypes(); } public function isCandidate(Tokens $tokens) { return $tokens->isTokenKindFound(T_DOC_COMMENT); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { foreach ($tokens as $index => $token) { if (!$token->isGivenKind(T_DOC_COMMENT)) { continue; } $doc = new DocBlock($token->getContent()); $annotations = $doc->getAnnotationsOfType($this->tags); if (empty($annotations)) { continue; } foreach ($annotations as $annotation) { $this->fixTypes($annotation); } $tokens[$index] = new Token([T_DOC_COMMENT, $doc->getContent()]); } } abstract protected function normalize($type); private function fixTypes(Annotation $annotation) { $types = $annotation->getTypes(); $new = $this->normalizeTypes($types); if ($types !== $new) { $annotation->setTypes($new); } } private function normalizeTypes(array $types) { foreach ($types as $index => $type) { $types[$index] = $this->normalizeType($type); } return $types; } private function normalizeType($type) { if ('[]' === substr($type, -2)) { return $this->normalize(substr($type, 0, -2)).'[]'; } return $this->normalize($type); } } isGivenKind(T_OPEN_TAG)) { $openingToken = $token; $openingTokenIndex = $index - $i; $newlineInOpening = false !== strpos($token->getContent(), "\n"); if ($newlineInOpening) { ++$precedingNewlines; } break; } if (false === $token->isGivenKind(T_WHITESPACE)) { break; } $precedingNewlines += substr_count($token->getContent(), "\n"); } } if ($precedingNewlines >= $expectedMin && $precedingNewlines <= $expectedMax) { return; } $previousIndex = $index - 1; $previous = $tokens[$previousIndex]; if (0 === $expectedMax) { if ($previous->isWhitespace()) { $tokens->clearAt($previousIndex); } if ($newlineInOpening) { $tokens[$openingTokenIndex] = new Token([T_OPEN_TAG, rtrim($openingToken->getContent()).' ']); } return; } $lineEnding = $this->whitespacesConfig->getLineEnding(); $newlinesForWhitespaceToken = $expectedMax; if (null !== $openingToken) { $content = rtrim($openingToken->getContent()); $newContent = $content.$lineEnding; $tokens[$openingTokenIndex] = new Token([T_OPEN_TAG, $newContent]); --$newlinesForWhitespaceToken; } if (0 === $newlinesForWhitespaceToken) { if ($previous->isWhitespace()) { $tokens->clearAt($previousIndex); } return; } if ($previous->isWhitespace()) { $tokens[$previousIndex] = new Token([T_WHITESPACE, str_repeat($lineEnding, $newlinesForWhitespaceToken).substr($previous->getContent(), strrpos($previous->getContent(), "\n") + 1)]); } else { $tokens->insertAt($index, new Token([T_WHITESPACE, str_repeat($lineEnding, $newlinesForWhitespaceToken)])); } } } isAllTokenKindsFound([T_STRING, T_CONSTANT_ENCAPSED_STRING]); } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { $argumentsAnalyzer = new ArgumentsAnalyzer(); $index = 0; $end = $tokens->count() - 1; while (true) { $candidate = $this->find('fopen', $tokens, $index, $end); if (null === $candidate) { break; } $index = $candidate[1]; $arguments = $argumentsAnalyzer->getArguments( $tokens, $index, $candidate[2] ); $argumentsCount = \count($arguments); if ($argumentsCount < 2 || $argumentsCount > 4) { continue; } $argumentStartIndex = array_keys($arguments)[1]; $this->fixFopenFlagToken( $tokens, $argumentStartIndex, $arguments[$argumentStartIndex] ); } } abstract protected function fixFopenFlagToken(Tokens $tokens, $argumentStartIndex, $argumentEndIndex); protected function isValidModeString($mode) { $modeLength = \strlen($mode); if ($modeLength < 1 || $modeLength > 13) { return false; } $validFlags = [ 'a' => true, 'b' => true, 'c' => true, 'e' => true, 'r' => true, 't' => true, 'w' => true, 'x' => true, ]; if (!isset($validFlags[$mode[0]])) { return false; } unset($validFlags[$mode[0]]); for ($i = 1; $i < $modeLength; ++$i) { if (isset($validFlags[$mode[$i]])) { unset($validFlags[$mode[$i]]); continue; } if ('+' !== $mode[$i] || ( 'a' !== $mode[$i - 1] && 'c' !== $mode[$i - 1] && 'r' !== $mode[$i - 1] && 'w' !== $mode[$i - 1] && 'x' !== $mode[$i - 1] ) ) { return false; } } return true; } } getMessage(); } return null; } } files() ->name('*.php') ->ignoreDotFiles(true) ->ignoreVCS(true) ->exclude('vendor') ; } } configure([]); } catch (RequiredFixerConfigurationException $e) { } } if ($this instanceof WhitespacesAwareFixerInterface) { $this->whitespacesConfig = $this->getDefaultWhitespacesFixerConfig(); } } final public function fix(\SplFileInfo $file, Tokens $tokens) { if ($this instanceof ConfigurableFixerInterface && null === $this->configuration) { throw new RequiredFixerConfigurationException($this->getName(), 'Configuration is required.'); } if (0 < $tokens->count() && $this->isCandidate($tokens) && $this->supports($file)) { $this->applyFix($file, $tokens); } } public function isRisky() { return false; } public function getName() { $nameParts = explode('\\', static::class); $name = substr(end($nameParts), 0, -\strlen('Fixer')); return Utils::camelCaseToUnderscore($name); } public function getPriority() { return 0; } public function supports(\SplFileInfo $file) { return true; } public function configure(array $configuration = null) { if (!$this instanceof ConfigurationDefinitionFixerInterface) { throw new \LogicException('Cannot configure using Abstract parent, child not implementing "PhpCsFixer\Fixer\ConfigurationDefinitionFixerInterface".'); } if (null === $configuration) { $message = 'Passing NULL to set default configuration is deprecated and will not be supported in 3.0, use an empty array instead.'; if (getenv('PHP_CS_FIXER_FUTURE_MODE')) { throw new \InvalidArgumentException("{$message} This check was performed as `PHP_CS_FIXER_FUTURE_MODE` env var is set."); } @trigger_error($message, E_USER_DEPRECATED); $configuration = []; } foreach ($this->getConfigurationDefinition()->getOptions() as $option) { if (!$option instanceof DeprecatedFixerOption) { continue; } $name = $option->getName(); if (\array_key_exists($name, $configuration)) { $message = sprintf( 'Option "%s" for rule "%s" is deprecated and will be removed in version %d.0. %s', $name, $this->getName(), Application::getMajorVersion() + 1, str_replace('`', '"', $option->getDeprecationMessage()) ); if (getenv('PHP_CS_FIXER_FUTURE_MODE')) { throw new \InvalidArgumentException("{$message} This check was performed as `PHP_CS_FIXER_FUTURE_MODE` env var is set."); } @trigger_error($message, E_USER_DEPRECATED); } } try { $this->configuration = $this->getConfigurationDefinition()->resolve($configuration); } catch (MissingOptionsException $exception) { throw new RequiredFixerConfigurationException( $this->getName(), sprintf('Missing required configuration: %s', $exception->getMessage()), $exception ); } catch (InvalidOptionsForEnvException $exception) { throw new InvalidForEnvFixerConfigurationException( $this->getName(), sprintf('Invalid configuration for env: %s', $exception->getMessage()), $exception ); } catch (ExceptionInterface $exception) { throw new InvalidFixerConfigurationException( $this->getName(), sprintf('Invalid configuration: %s', $exception->getMessage()), $exception ); } } public function getConfigurationDefinition() { if (!$this instanceof ConfigurationDefinitionFixerInterface) { throw new \LogicException('Cannot get configuration definition using Abstract parent, child not implementing "PhpCsFixer\Fixer\ConfigurationDefinitionFixerInterface".'); } if (null === $this->configurationDefinition) { $this->configurationDefinition = $this->createConfigurationDefinition(); } return $this->configurationDefinition; } public function setWhitespacesConfig(WhitespacesFixerConfig $config) { if (!$this instanceof WhitespacesAwareFixerInterface) { throw new \LogicException('Cannot run method for class not implementing "PhpCsFixer\Fixer\WhitespacesAwareFixerInterface".'); } $this->whitespacesConfig = $config; } abstract protected function applyFix(\SplFileInfo $file, Tokens $tokens); protected function createConfigurationDefinition() { if (!$this instanceof ConfigurationDefinitionFixerInterface) { throw new \LogicException('Cannot create configuration definition using Abstract parent, child not implementing "PhpCsFixer\Fixer\ConfigurationDefinitionFixerInterface".'); } throw new \LogicException('Not implemented.'); } private function getDefaultWhitespacesFixerConfig() { static $defaultWhitespacesFixerConfig = null; if (null === $defaultWhitespacesFixerConfig) { $defaultWhitespacesFixerConfig = new WhitespacesFixerConfig(' ', "\n"); } return $defaultWhitespacesFixerConfig; } } stdinContent) { $this->stdinContent = $this->readRaw($filePath); } return $this->stdinContent; } return $this->readRaw($filePath); } private function readRaw($realPath) { $content = @file_get_contents($realPath); if (false === $content) { $error = error_get_last(); throw new \RuntimeException(sprintf( 'Failed to read content from "%s".%s', $realPath, $error ? ' '.$error['message'] : '' )); } return $content; } } getRealPath(); } public function getRealPath() { return 'php://stdin'; } public function getATime() { return 0; } public function getBasename($suffix = null) { return $this->getFilename(); } public function getCTime() { return 0; } public function getExtension() { return '.php'; } public function getFileInfo($className = null) { throw new \BadMethodCallException(sprintf('Method "%s" is not implemented.', __METHOD__)); } public function getFilename() { return 'stdin.php'; } public function getGroup() { return 0; } public function getInode() { return 0; } public function getLinkTarget() { return ''; } public function getMTime() { return 0; } public function getOwner() { return 0; } public function getPath() { return ''; } public function getPathInfo($className = null) { throw new \BadMethodCallException(sprintf('Method "%s" is not implemented.', __METHOD__)); } public function getPathname() { return $this->getFilename(); } public function getPerms() { return 0; } public function getSize() { return 0; } public function getType() { return 'file'; } public function isDir() { return false; } public function isExecutable() { return false; } public function isFile() { return true; } public function isLink() { return false; } public function isReadable() { return true; } public function isWritable() { return false; } public function openFile($openMode = 'r', $useIncludePath = false, $context = null) { throw new \BadMethodCallException(sprintf('Method "%s" is not implemented.', __METHOD__)); } public function setFileClass($className = null) { } public function setInfoClass($className = null) { } } getPreviousBlock($tokens, $previousBlockStart); $previous = $previousBlockEnd; if ($tokens[$previous]->equals('}')) { $previous = $tokens->getPrevMeaningfulToken($previous); } if ( !$tokens[$previous]->equals(';') || $tokens[$tokens->getPrevMeaningfulToken($previous)]->equals('{') ) { return false; } $candidateIndex = $tokens->getPrevTokenOfKind( $previous, [ ';', [T_BREAK], [T_CLOSE_TAG], [T_CONTINUE], [T_EXIT], [T_GOTO], [T_IF], [T_RETURN], [T_THROW], ] ); if ( null === $candidateIndex || $tokens[$candidateIndex]->equalsAny([';', [T_CLOSE_TAG], [T_IF]]) || $this->isInConditional($tokens, $candidateIndex, $previousBlockStart) || $this->isInConditionWithoutBraces($tokens, $candidateIndex, $previousBlockStart) ) { return false; } } while (!$tokens[$previousBlockStart]->isGivenKind(T_IF)); return true; } private function getPreviousBlock(Tokens $tokens, $index) { $close = $previous = $tokens->getPrevMeaningfulToken($index); if ($tokens[$close]->equals('}')) { $previous = $tokens->findBlockStart(Tokens::BLOCK_TYPE_CURLY_BRACE, $close); } $open = $tokens->getPrevTokenOfKind($previous, [[T_IF], [T_ELSE], [T_ELSEIF]]); if ($tokens[$open]->isGivenKind(T_IF)) { $elseCandidate = $tokens->getPrevMeaningfulToken($open); if ($tokens[$elseCandidate]->isGivenKind(T_ELSE)) { $open = $elseCandidate; } } return [$open, $close]; } private function isInConditional(Tokens $tokens, $index, $lowerLimitIndex) { $candidateIndex = $tokens->getPrevTokenOfKind($index, [')', ';', ':']); if ($tokens[$candidateIndex]->equals(':')) { return true; } if (!$tokens[$candidateIndex]->equals(')')) { return false; } $open = $tokens->findBlockStart(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $candidateIndex); return $tokens->getPrevMeaningfulToken($open) > $lowerLimitIndex; } private function isInConditionWithoutBraces(Tokens $tokens, $index, $lowerLimitIndex) { do { if ($tokens[$index]->isComment() || $tokens[$index]->isWhitespace()) { $index = $tokens->getPrevMeaningfulToken($index); } $token = $tokens[$index]; if ($token->isGivenKind([T_IF, T_ELSEIF, T_ELSE])) { return true; } if ($token->equals(';')) { return false; } if ($token->equals('{')) { $index = $tokens->getPrevMeaningfulToken($index); if ($tokens[$index]->isGivenKind(T_DO)) { --$index; continue; } if (!$tokens[$index]->equals(')')) { return false; } $index = $tokens->findBlockStart( Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index ); $index = $tokens->getPrevMeaningfulToken($index); if ($tokens[$index]->isGivenKind([T_IF, T_ELSEIF])) { return false; } } elseif ($token->equals(')')) { $type = Tokens::detectBlockType($token); $index = $tokens->findBlockStart( $type['type'], $index ); $index = $tokens->getPrevMeaningfulToken($index); } else { --$index; } } while ($index > $lowerLimitIndex); return false; } } errors, static function (Error $error) { return Error::TYPE_INVALID === $error->getType(); }); } public function getExceptionErrors() { return array_filter($this->errors, static function (Error $error) { return Error::TYPE_EXCEPTION === $error->getType(); }); } public function getLintErrors() { return array_filter($this->errors, static function (Error $error) { return Error::TYPE_LINT === $error->getType(); }); } public function isEmpty() { return empty($this->errors); } public function report(Error $error) { $this->errors[] = $error; } } type = $type; $this->filePath = $filePath; $this->source = $source; $this->appliedFixers = $appliedFixers; $this->diff = $diff; } public function getFilePath() { return $this->filePath; } public function getSource() { return $this->source; } public function getType() { return $this->type; } public function getAppliedFixers() { return $this->appliedFixers; } public function getDiff() { return $this->diff; } } appendChild($dom->createElement('testsuites')); $testsuite = $testsuites->appendChild($dom->createElement('testsuite')); $testsuite->setAttribute('name', 'PHP CS Fixer'); if (\count($reportSummary->getChanged())) { $this->createFailedTestCases($dom, $testsuite, $reportSummary); } else { $this->createSuccessTestCase($dom, $testsuite); } if ($reportSummary->getTime()) { $testsuite->setAttribute( 'time', sprintf( '%.3f', $reportSummary->getTime() / 1000 ) ); } $dom->formatOutput = true; return $reportSummary->isDecoratedOutput() ? OutputFormatter::escape($dom->saveXML()) : $dom->saveXML(); } private function createSuccessTestCase(\DOMDocument $dom, \DOMElement $testsuite) { $testcase = $dom->createElement('testcase'); $testcase->setAttribute('name', 'All OK'); $testcase->setAttribute('assertions', '1'); $testsuite->appendChild($testcase); $testsuite->setAttribute('tests', '1'); $testsuite->setAttribute('assertions', '1'); $testsuite->setAttribute('failures', '0'); $testsuite->setAttribute('errors', '0'); } private function createFailedTestCases(\DOMDocument $dom, \DOMElement $testsuite, ReportSummary $reportSummary) { $assertionsCount = 0; foreach ($reportSummary->getChanged() as $file => $fixResult) { $testcase = $this->createFailedTestCase( $dom, $file, $fixResult, $reportSummary->shouldAddAppliedFixers() ); $testsuite->appendChild($testcase); $assertionsCount += (int) $testcase->getAttribute('assertions'); } $testsuite->setAttribute('tests', (string) \count($reportSummary->getChanged())); $testsuite->setAttribute('assertions', (string) $assertionsCount); $testsuite->setAttribute('failures', (string) $assertionsCount); $testsuite->setAttribute('errors', '0'); } private function createFailedTestCase(\DOMDocument $dom, $file, array $fixResult, $shouldAddAppliedFixers) { $appliedFixersCount = \count($fixResult['appliedFixers']); $testName = str_replace('.', '_DOT_', Preg::replace('@\.'.pathinfo($file, PATHINFO_EXTENSION).'$@', '', $file)); $testcase = $dom->createElement('testcase'); $testcase->setAttribute('name', $testName); $testcase->setAttribute('file', $file); $testcase->setAttribute('assertions', (string) $appliedFixersCount); $failure = $dom->createElement('failure'); $failure->setAttribute('type', 'code_style'); $testcase->appendChild($failure); if ($shouldAddAppliedFixers) { $failureContent = "applied fixers:\n---------------\n"; foreach ($fixResult['appliedFixers'] as $appliedFixer) { $failureContent .= "* {$appliedFixer}\n"; } } else { $failureContent = "Wrong code style\n"; } if (!empty($fixResult['diff'])) { $failureContent .= "\nDiff:\n---------------\n\n".$fixResult['diff']; } $failure->appendChild($dom->createCDATASection(trim($failureContent))); return $testcase; } } appendChild($dom->createElement('checkstyle')); foreach ($reportSummary->getChanged() as $filePath => $fixResult) { $file = $checkstyles->appendChild($dom->createElement('file')); $file->setAttribute('name', $filePath); foreach ($fixResult['appliedFixers'] as $appliedFixer) { $error = $this->createError($dom, $appliedFixer); $file->appendChild($error); } } $dom->formatOutput = true; return $reportSummary->isDecoratedOutput() ? OutputFormatter::escape($dom->saveXML()) : $dom->saveXML(); } private function createError(\DOMDocument $dom, $appliedFixer) { $error = $dom->createElement('error'); $error->setAttribute('severity', 'warning'); $error->setAttribute('source', 'PHP-CS-Fixer.'.$appliedFixer); $error->setAttribute('message', 'Found violation(s) of type: '.$appliedFixer); return $error; } } changed = $changed; $this->time = $time; $this->memory = $memory; $this->addAppliedFixers = $addAppliedFixers; $this->isDryRun = $isDryRun; $this->isDecoratedOutput = $isDecoratedOutput; } public function isDecoratedOutput() { return $this->isDecoratedOutput; } public function isDryRun() { return $this->isDryRun; } public function getChanged() { return $this->changed; } public function getMemory() { return $this->memory; } public function getTime() { return $this->time; } public function shouldAddAppliedFixers() { return $this->addAppliedFixers; } } getChanged() as $file => $fixResult) { ++$i; $output .= sprintf('%4d) %s', $i, $file); if ($reportSummary->shouldAddAppliedFixers()) { $output .= $this->getAppliedFixers($reportSummary->isDecoratedOutput(), $fixResult); } $output .= $this->getDiff($reportSummary->isDecoratedOutput(), $fixResult); $output .= PHP_EOL; } return $output.$this->getFooter($reportSummary->getTime(), $reportSummary->getMemory(), $reportSummary->isDryRun()); } private function getAppliedFixers($isDecoratedOutput, array $fixResult) { return sprintf( $isDecoratedOutput ? ' (%s)' : ' (%s)', implode(', ', $fixResult['appliedFixers']) ); } private function getDiff($isDecoratedOutput, array $fixResult) { if (empty($fixResult['diff'])) { return ''; } $diffFormatter = new DiffConsoleFormatter($isDecoratedOutput, sprintf( ' ---------- begin diff ----------%s%%s%s ----------- end diff -----------', PHP_EOL, PHP_EOL )); return PHP_EOL.$diffFormatter->format($fixResult['diff']).PHP_EOL; } private function getFooter($time, $memory, $isDryRun) { if (0 === $time || 0 === $memory) { return ''; } return PHP_EOL.sprintf( '%s all files in %.3f seconds, %.3f MB memory used'.PHP_EOL, $isDryRun ? 'Checked' : 'Fixed', $time / 1000, $memory / 1024 / 1024 ); } } getChanged() as $file => $fixResult) { $jfile = ['name' => $file]; if ($reportSummary->shouldAddAppliedFixers()) { $jfile['appliedFixers'] = $fixResult['appliedFixers']; } if (!empty($fixResult['diff'])) { $jfile['diff'] = $fixResult['diff']; } $jFiles[] = $jfile; } $json = [ 'files' => $jFiles, ]; if (null !== $reportSummary->getTime()) { $json['time'] = [ 'total' => round($reportSummary->getTime() / 1000, 3), ]; } if (null !== $reportSummary->getMemory()) { $json['memory'] = round($reportSummary->getMemory() / 1024 / 1024, 3); } $json = json_encode($json); return $reportSummary->isDecoratedOutput() ? OutputFormatter::escape($json) : $json; } } getChanged() as $fileName => $change) { foreach ($change['appliedFixers'] as $fixerName) { $report[] = [ 'description' => $fixerName, 'fingerprint' => md5($fileName.$fixerName), 'location' => [ 'path' => $fileName, 'lines' => [ 'begin' => 0, ], ], ]; } } $jsonString = json_encode($report); return $reportSummary->isDecoratedOutput() ? OutputFormatter::escape($jsonString) : $jsonString; } } files()->name('*Reporter.php')->in(__DIR__) as $file) { $relativeNamespace = $file->getRelativePath(); $builtInReporters[] = sprintf( '%s\\%s%s', __NAMESPACE__, $relativeNamespace ? $relativeNamespace.'\\' : '', $file->getBasename('.php') ); } } foreach ($builtInReporters as $reporterClass) { $this->registerReporter(new $reporterClass()); } return $this; } public function registerReporter(ReporterInterface $reporter) { $format = $reporter->getFormat(); if (isset($this->reporters[$format])) { throw new \UnexpectedValueException(sprintf('Reporter for format "%s" is already registered.', $format)); } $this->reporters[$format] = $reporter; return $this; } public function getFormats() { $formats = array_keys($this->reporters); sort($formats); return $formats; } public function getReporter($format) { if (!isset($this->reporters[$format])) { throw new \UnexpectedValueException(sprintf('Reporter for format "%s" is not registered.', $format)); } return $this->reporters[$format]; } } createElement('report'); $dom->appendChild($root); $filesXML = $dom->createElement('files'); $root->appendChild($filesXML); $i = 1; foreach ($reportSummary->getChanged() as $file => $fixResult) { $fileXML = $dom->createElement('file'); $fileXML->setAttribute('id', (string) $i++); $fileXML->setAttribute('name', $file); $filesXML->appendChild($fileXML); if ($reportSummary->shouldAddAppliedFixers()) { $fileXML->appendChild($this->createAppliedFixersElement($dom, $fixResult)); } if (!empty($fixResult['diff'])) { $fileXML->appendChild($this->createDiffElement($dom, $fixResult)); } } if (0 !== $reportSummary->getTime()) { $root->appendChild($this->createTimeElement($reportSummary->getTime(), $dom)); } if (0 !== $reportSummary->getMemory()) { $root->appendChild($this->createMemoryElement($reportSummary->getMemory(), $dom)); } $dom->formatOutput = true; return $reportSummary->isDecoratedOutput() ? OutputFormatter::escape($dom->saveXML()) : $dom->saveXML(); } private function createAppliedFixersElement($dom, array $fixResult) { $appliedFixersXML = $dom->createElement('applied_fixers'); foreach ($fixResult['appliedFixers'] as $appliedFixer) { $appliedFixerXML = $dom->createElement('applied_fixer'); $appliedFixerXML->setAttribute('name', $appliedFixer); $appliedFixersXML->appendChild($appliedFixerXML); } return $appliedFixersXML; } private function createDiffElement(\DOMDocument $dom, array $fixResult) { $diffXML = $dom->createElement('diff'); $diffXML->appendChild($dom->createCDATASection($fixResult['diff'])); return $diffXML; } private function createTimeElement($time, \DOMDocument $dom) { $time = round($time / 1000, 3); $timeXML = $dom->createElement('time'); $timeXML->setAttribute('unit', 's'); $timeTotalXML = $dom->createElement('total'); $timeTotalXML->setAttribute('value', (string) $time); $timeXML->appendChild($timeTotalXML); return $timeXML; } private function createMemoryElement($memory, \DOMDocument $dom) { $memory = round($memory / 1024 / 1024, 3); $memoryXML = $dom->createElement('memory'); $memoryXML->setAttribute('value', (string) $memory); $memoryXML->setAttribute('unit', 'MB'); return $memoryXML; } } differ = new Differ("--- Original\n+++ New\n", false); } public function diff($old, $new) { return $this->differ->diff($old, $new); } } differ = new Differ(); } public function diff($old, $new) { return $this->differ->diff($old, $new); } } differ = new Differ(new StrictUnifiedDiffOutputBuilder([ 'fromFile' => 'Original', 'toFile' => 'New', ])); } public function diff($old, $new) { return $this->differ->diff($old, $new); } } differ = new Differ(new StrictUnifiedDiffOutputBuilder([ 'collapseRanges' => false, 'commonLineThreshold' => 100, 'contextLines' => 100, 'fromFile' => 'Original', 'toFile' => 'New', ])); } public function diff($old, $new) { return $this->differ->diff($old, $new); } } isDecoratedOutput = $isDecoratedOutput; $this->template = $template; } public function format($diff, $lineTemplate = '%s') { $isDecorated = $this->isDecoratedOutput; $template = $isDecorated ? $this->template : Preg::replace('/<[^<>]+>/', '', $this->template) ; return sprintf( $template, implode( PHP_EOL, array_map( static function ($line) use ($isDecorated, $lineTemplate) { if ($isDecorated) { $count = 0; $line = Preg::replaceCallback( [ '/^(\+.*)/', '/^(\-.*)/', '/^(@.*)/', ], static function ($matches) { if ('+' === $matches[0][0]) { $colour = 'green'; } elseif ('-' === $matches[0][0]) { $colour = 'red'; } else { $colour = 'cyan'; } return sprintf('%s', $colour, OutputFormatter::escape($matches[0]), $colour); }, $line, 1, $count ); if (0 === $count) { $line = OutputFormatter::escape($line); } } return sprintf($lineTemplate, $line); }, Preg::split('#\R#u', $diff) ) ) ); } } isGivenKind(T_DOC_COMMENT)) { throw new \InvalidArgumentException('Input must be a T_DOC_COMMENT token.'); } $tokens = new self(); $content = $input->getContent(); $ignoredTextPosition = 0; $currentPosition = 0; while (false !== $nextAtPosition = strpos($content, '@', $currentPosition)) { if (0 !== $nextAtPosition && !Preg::match('/\s/', $content[$nextAtPosition - 1])) { $currentPosition = $nextAtPosition + 1; continue; } $lexer = new DocLexer(); $lexer->setInput(substr($content, $nextAtPosition)); $scannedTokens = []; $index = 0; $nbScannedTokensToUse = 0; $nbScopes = 0; while (null !== $token = $lexer->peek()) { if (0 === $index && DocLexer::T_AT !== $token['type']) { break; } if (1 === $index) { if (DocLexer::T_IDENTIFIER !== $token['type'] || \in_array($token['value'], $ignoredTags, true)) { break; } $nbScannedTokensToUse = 2; } if ($index >= 2 && 0 === $nbScopes && !\in_array($token['type'], [DocLexer::T_NONE, DocLexer::T_OPEN_PARENTHESIS], true)) { break; } $scannedTokens[] = $token; if (DocLexer::T_OPEN_PARENTHESIS === $token['type']) { ++$nbScopes; } elseif (DocLexer::T_CLOSE_PARENTHESIS === $token['type']) { if (0 === --$nbScopes) { $nbScannedTokensToUse = \count($scannedTokens); break; } } ++$index; } if (0 !== $nbScopes) { break; } if (0 !== $nbScannedTokensToUse) { $ignoredTextLength = $nextAtPosition - $ignoredTextPosition; if (0 !== $ignoredTextLength) { $tokens[] = new Token(DocLexer::T_NONE, substr($content, $ignoredTextPosition, $ignoredTextLength)); } $lastTokenEndIndex = 0; foreach (\array_slice($scannedTokens, 0, $nbScannedTokensToUse) as $token) { if (DocLexer::T_STRING === $token['type']) { $token['value'] = '"'.str_replace('"', '""', $token['value']).'"'; } $missingTextLength = $token['position'] - $lastTokenEndIndex; if ($missingTextLength > 0) { $tokens[] = new Token(DocLexer::T_NONE, substr( $content, $nextAtPosition + $lastTokenEndIndex, $missingTextLength )); } $tokens[] = new Token($token['type'], $token['value']); $lastTokenEndIndex = $token['position'] + \strlen($token['value']); } $currentPosition = $ignoredTextPosition = $nextAtPosition + $token['position'] + \strlen($token['value']); } else { $currentPosition = $nextAtPosition + 1; } } if ($ignoredTextPosition < \strlen($content)) { $tokens[] = new Token(DocLexer::T_NONE, substr($content, $ignoredTextPosition)); } return $tokens; } public function getNextMeaningfulToken($index) { return $this->getMeaningfulTokenSibling($index, 1); } public function getPreviousMeaningfulToken($index) { return $this->getMeaningfulTokenSibling($index, -1); } public function getNextTokenOfType($type, $index) { return $this->getTokenOfTypeSibling($index, $type, 1); } public function getPreviousTokenOfType($type, $index) { return $this->getTokenOfTypeSibling($index, $type, -1); } public function getAnnotationEnd($index) { $currentIndex = null; if (isset($this[$index + 2])) { if ($this[$index + 2]->isType(DocLexer::T_OPEN_PARENTHESIS)) { $currentIndex = $index + 2; } elseif ( isset($this[$index + 3]) && $this[$index + 2]->isType(DocLexer::T_NONE) && $this[$index + 3]->isType(DocLexer::T_OPEN_PARENTHESIS) && Preg::match('/^(\R\s*\*\s*)*\s*$/', $this[$index + 2]->getContent()) ) { $currentIndex = $index + 3; } } if (null !== $currentIndex) { $level = 0; for ($max = \count($this); $currentIndex < $max; ++$currentIndex) { if ($this[$currentIndex]->isType(DocLexer::T_OPEN_PARENTHESIS)) { ++$level; } elseif ($this[$currentIndex]->isType(DocLexer::T_CLOSE_PARENTHESIS)) { --$level; } if (0 === $level) { return $currentIndex; } } return null; } return $index + 1; } public function getArrayEnd($index) { $level = 1; for (++$index, $max = \count($this); $index < $max; ++$index) { if ($this[$index]->isType(DocLexer::T_OPEN_CURLY_BRACES)) { ++$level; } elseif ($this[$index]->isType($index, DocLexer::T_CLOSE_CURLY_BRACES)) { --$level; } if (0 === $level) { return $index; } } return null; } public function getCode() { $code = ''; foreach ($this as $token) { $code .= $token->getContent(); } return $code; } public function insertAt($index, Token $token) { $this->setSize($this->getSize() + 1); for ($i = $this->getSize() - 1; $i > $index; --$i) { $this[$i] = isset($this[$i - 1]) ? $this[$i - 1] : new Token(); } $this[$index] = $token; } public function offsetSet($index, $token) { if (!$token instanceof Token) { $type = \gettype($token); if ('object' === $type) { $type = \get_class($token); } throw new \InvalidArgumentException(sprintf( 'Token must be an instance of PhpCsFixer\\Doctrine\\Annotation\\Token, %s given.', $type )); } if (null === $index) { $index = \count($this); $this->setSize($this->getSize() + 1); } parent::offsetSet($index, $token); } public function offsetUnset($index) { if (!isset($this[$index])) { throw new \OutOfBoundsException(sprintf('Index %s is invalid or does not exist.', $index)); } $max = \count($this) - 1; while ($index < $max) { $this[$index] = $this[$index + 1]; ++$index; } parent::offsetUnset($index); $this->setSize($max); } private function getMeaningfulTokenSibling($index, $direction) { while (true) { $index += $direction; if (!$this->offsetExists($index)) { break; } if (!$this[$index]->isType(DocLexer::T_NONE)) { return $index; } } return null; } private function getTokenOfTypeSibling($index, $type, $direction) { while (true) { $index += $direction; if (!$this->offsetExists($index)) { break; } if ($this[$index]->isType($type)) { return $index; } } return null; } } type = $type; $this->content = $content; } public function getType() { return $this->type; } public function setType($type) { $this->type = $type; } public function getContent() { return $this->content; } public function setContent($content) { $this->content = $content; } public function isType($types) { if (!\is_array($types)) { $types = [$types]; } return \in_array($this->getType(), $types, true); } public function clear() { $this->setContent(''); } } doc = $doc; } public function getEnd() { $reachedContent = false; foreach ($this->doc->getLines() as $index => $line) { if ($reachedContent && ($line->containsATag() || !$line->containsUsefulContent())) { return $index - 1; } if ($line->containsATag()) { return null; } if ($line->containsUsefulContent()) { $reachedContent = true; } } return null; } } is any non-array, non-generic, non-alternated type, eg `int` or `\Foo` # is array of , eg `int[]` or `\Foo[]` # is generic collection type, like `array`, `Collection` and more complex like `Collection>` # is , or type, like `int`, `bool[]` or `Collection` # is one or more types alternated via `|`, like `int|bool[]|Collection` (? (? (? (?&simple)(\[\])* ) | (? [@$?]?[\\\\\w]+ ) | (? (?&simple) < (?:(?&types),\s*)?(?:(?&types)|(?&generic)) > ) ) (?: \| (?:(?&simple)|(?&array)|(?&generic)) )* ) '; private static $tags = [ 'method', 'param', 'property', 'property-read', 'property-write', 'return', 'throws', 'type', 'var', ]; private $lines; private $start; private $end; private $tag; private $typesContent; private $types; public function __construct(array $lines) { $this->lines = array_values($lines); $keys = array_keys($lines); $this->start = $keys[0]; $this->end = end($keys); } public function __toString() { return $this->getContent(); } public static function getTagsWithTypes() { return self::$tags; } public function getStart() { return $this->start; } public function getEnd() { return $this->end; } public function getTag() { if (null === $this->tag) { $this->tag = new Tag($this->lines[0]); } return $this->tag; } public function getTypes() { if (null === $this->types) { $this->types = []; $content = $this->getTypesContent(); while ('' !== $content && false !== $content) { Preg::match( '{^'.self::REGEX_TYPES.'$}x', $content, $matches ); $this->types[] = $matches['type']; $content = substr($content, \strlen($matches['type']) + 1); } } return $this->types; } public function setTypes(array $types) { $pattern = '/'.preg_quote($this->getTypesContent(), '/').'/'; $this->lines[0]->setContent(Preg::replace($pattern, implode('|', $types), $this->lines[0]->getContent(), 1)); $this->clearCache(); } public function getNormalizedTypes() { $normalized = array_map(static function ($type) { return strtolower($type); }, $this->getTypes()); sort($normalized); return $normalized; } public function remove() { foreach ($this->lines as $line) { $line->remove(); } $this->clearCache(); } public function getContent() { return implode('', $this->lines); } public function supportTypes() { return \in_array($this->getTag()->getName(), self::$tags, true); } private function getTypesContent() { if (null === $this->typesContent) { $name = $this->getTag()->getName(); if (!$this->supportTypes()) { throw new \RuntimeException('This tag does not support types.'); } $matchingResult = Preg::match( '{^(?:\s*\*|/\*\*)\s*@'.$name.'\s+'.self::REGEX_TYPES.'(?:\h.*)?$}sx', $this->lines[0]->getContent(), $matches ); $this->typesContent = 1 === $matchingResult ? $matches['types'] : ''; } return $this->typesContent; } private function clearCache() { $this->types = null; $this->typesContent = null; } } line = $line; } public function getName() { if (null === $this->name) { Preg::matchAll('/@[a-zA-Z0-9_-]+(?=\s|$)/', $this->line->getContent(), $matches); if (isset($matches[0][0])) { $this->name = ltrim($matches[0][0], '@'); } else { $this->name = 'other'; } } return $this->name; } public function setName($name) { $current = $this->getName(); if ('other' === $current) { throw new \RuntimeException('Cannot set name on unknown tag.'); } $this->line->setContent(Preg::replace("/@{$current}/", "@{$name}", $this->line->getContent(), 1)); $this->name = $name; } public function valid() { return \in_array($this->getName(), self::$tags, true); } } lines[] = new Line($line); } } public function __toString() { return $this->getContent(); } public function getLines() { return $this->lines; } public function getLine($pos) { return isset($this->lines[$pos]) ? $this->lines[$pos] : null; } public function getAnnotations() { if (null === $this->annotations) { $this->annotations = []; $total = \count($this->lines); for ($index = 0; $index < $total; ++$index) { if ($this->lines[$index]->containsATag()) { $lines = \array_slice($this->lines, $index, $this->findAnnotationLength($index), true); $annotation = new Annotation($lines); $index = $annotation->getEnd(); $this->annotations[] = $annotation; } } } return $this->annotations; } public function getAnnotation($pos) { $annotations = $this->getAnnotations(); return isset($annotations[$pos]) ? $annotations[$pos] : null; } public function getAnnotationsOfType($types) { $annotations = []; $types = (array) $types; foreach ($this->getAnnotations() as $annotation) { $tag = $annotation->getTag()->getName(); foreach ($types as $type) { if ($type === $tag) { $annotations[] = $annotation; } } } return $annotations; } public function getContent() { return implode('', $this->lines); } private function findAnnotationLength($start) { $index = $start; while ($line = $this->getLine(++$index)) { if ($line->containsATag()) { break; } if (!$line->containsUsefulContent()) { $next = $this->getLine($index + 1); if (null === $next || !$next->containsUsefulContent() || $next->containsATag()) { break; } } } return $index - $start; } } getName(); $secondName = $second->getName(); if ($firstName === $secondName) { return true; } foreach (self::$groups as $group) { if (\in_array($firstName, $group, true) && \in_array($secondName, $group, true)) { return true; } } return false; } } content = $content; } public function __toString() { return $this->content; } public function getContent() { return $this->content; } public function containsUsefulContent() { return 0 !== Preg::match('/\\*\s*\S+/', $this->content) && !$this->isTheStart() && !$this->isTheEnd(); } public function containsATag() { return 0 !== Preg::match('/\\*\s*@/', $this->content); } public function isTheStart() { return false !== strpos($this->content, '/**'); } public function isTheEnd() { return false !== strpos($this->content, '*/'); } public function setContent($content) { $this->content = $content; } public function remove() { $this->content = ''; } public function addBlank() { $matched = Preg::match('/^(\h*\*)[^\r\n]*(\r?\n)$/', $this->content, $matches); if (1 !== $matched) { return; } $this->content .= $matches[1].$matches[2]; } } count() : $end; $candidateSequence = [[T_STRING, $functionNameToSearch], '(']; $matches = $tokens->findSequence($candidateSequence, $start, $end, false); if (null === $matches) { return null; } list($functionName, $openParenthesis) = array_keys($matches); $functionsAnalyzer = new FunctionsAnalyzer(); if (!$functionsAnalyzer->isGlobalFunctionCall($tokens, $functionName)) { return $this->find($functionNameToSearch, $tokens, $openParenthesis, $end); } return [$functionName, $openParenthesis, $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openParenthesis)]; } } status = $status; } public function getStatus() { return $this->status; } } createProxyFixers()) as $proxyFixer) { $this->proxyFixers[$proxyFixer->getName()] = $proxyFixer; } parent::__construct(); } public function isCandidate(Tokens $tokens) { foreach ($this->proxyFixers as $fixer) { if ($fixer->isCandidate($tokens)) { return true; } } return false; } public function isRisky() { foreach ($this->proxyFixers as $fixer) { if ($fixer->isRisky()) { return true; } } return false; } public function getPriority() { if (\count($this->proxyFixers) > 1) { throw new \LogicException('You need to override this method to provide the priority of combined fixers.'); } return reset($this->proxyFixers)->getPriority(); } public function supports(\SplFileInfo $file) { foreach ($this->proxyFixers as $fixer) { if ($fixer->supports($file)) { return true; } } return false; } public function setWhitespacesConfig(WhitespacesFixerConfig $config) { parent::setWhitespacesConfig($config); foreach ($this->proxyFixers as $fixer) { if ($fixer instanceof WhitespacesAwareFixerInterface) { $fixer->setWhitespacesConfig($config); } } } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { foreach ($this->proxyFixers as $fixer) { $fixer->fix($file, $tokens); } } abstract protected function createProxyFixers(); } fixerName = $fixerName; } public function getFixerName() { return $this->fixerName; } } isWhitespace()) { throw new \InvalidArgumentException(sprintf('The given token must be whitespace, got "%s".', $token->getName())); } $str = strrchr( str_replace(["\r\n", "\r"], "\n", $token->getContent()), "\n" ); if (false === $str) { return ''; } return ltrim($str, "\n"); } public static function stableSort(array $elements, callable $getComparedValue, callable $compareValues) { array_walk($elements, static function (&$element, $index) use ($getComparedValue) { $element = [$element, $index, $getComparedValue($element)]; }); usort($elements, static function ($a, $b) use ($compareValues) { $comparison = $compareValues($a[2], $b[2]); if (0 !== $comparison) { return $comparison; } return self::cmpInt($a[1], $b[1]); }); return array_map(static function (array $item) { return $item[0]; }, $elements); } public static function sortFixers(array $fixers) { return self::stableSort( $fixers, static function (FixerInterface $fixer) { return $fixer->getPriority(); }, static function ($a, $b) { return self::cmpInt($b, $a); } ); } public static function naturalLanguageJoinWithBackticks(array $names) { if (empty($names)) { throw new \InvalidArgumentException('Array of names cannot be empty'); } $names = array_map(static function ($name) { return sprintf('`%s`', $name); }, $names); $last = array_pop($names); if ($names) { return implode(', ', $names).' and '.$last; } return $last; } } indent = $indent; $this->lineEnding = $lineEnding; } public function getIndent() { return $this->indent; } public function getLineEnding() { return $this->lineEnding; } } injectAlignmentPlaceholders($tokensClone, 0, \count($tokens)); $content = $this->replacePlaceholder($tokensClone); $tokens->setCode($content); } abstract protected function injectAlignmentPlaceholders(Tokens $tokens, $startAt, $endAt); protected function replacePlaceholder(Tokens $tokens) { $tmpCode = $tokens->generateCode(); for ($j = 0; $j <= $this->deepestLevel; ++$j) { $placeholder = sprintf(self::ALIGNABLE_PLACEHOLDER, $j); if (false === strpos($tmpCode, $placeholder)) { continue; } $lines = explode("\n", $tmpCode); $linesWithPlaceholder = []; $blockSize = 0; $linesWithPlaceholder[$blockSize] = []; foreach ($lines as $index => $line) { if (substr_count($line, $placeholder) > 0) { $linesWithPlaceholder[$blockSize][] = $index; } else { ++$blockSize; $linesWithPlaceholder[$blockSize] = []; } } foreach ($linesWithPlaceholder as $group) { if (\count($group) < 1) { continue; } $rightmostSymbol = 0; foreach ($group as $index) { $rightmostSymbol = max($rightmostSymbol, strpos(utf8_decode($lines[$index]), $placeholder)); } foreach ($group as $index) { $line = $lines[$index]; $currentSymbol = strpos(utf8_decode($line), $placeholder); $delta = abs($rightmostSymbol - $currentSymbol); if ($delta > 0) { $line = str_replace($placeholder, str_repeat(' ', $delta).$placeholder, $line); $lines[$index] = $line; } } } $tmpCode = str_replace($placeholder, '', implode("\n", $lines)); } return $tmpCode; } } isGivenKind(T_CLASS)) { throw new \LogicException(sprintf('No T_CLASS at given index %d, got %s.', $index, $tokens[$index]->getName())); } $index = $tokens->getNextMeaningfulToken($index); if (0 !== Preg::match('/(?:Test|TestCase)$/', $tokens[$index]->getContent())) { return true; } while (null !== $index = $tokens->getNextMeaningfulToken($index)) { if ($tokens[$index]->equals('{')) { break; } if (!$tokens[$index]->isGivenKind(T_STRING)) { continue; } if (0 !== Preg::match('/(?:Test|TestCase)(?:Interface)?$/', $tokens[$index]->getContent())) { return true; } } return false; } public function findPhpUnitClasses(Tokens $tokens, $beginAtBottom = true) { $direction = $beginAtBottom ? -1 : 1; for ($index = 1 === $direction ? 0 : $tokens->count() - 1; $tokens->offsetExists($index); $index += $direction) { if (!$tokens[$index]->isGivenKind(T_CLASS) || !$this->isPhpUnitClass($tokens, $index)) { continue; } $startIndex = $tokens->getNextTokenOfKind($index, ['{'], false); if (null === $startIndex) { return; } $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $startIndex); yield [$startIndex, $endIndex]; } } } isInstalledByComposer()) { throw new \LogicException('Cannot get composer version for tool not installed by composer.'); } if (null === $this->composerInstallationDetails) { $composerInstalled = json_decode(file_get_contents($this->getComposerInstalledFile()), true); $packages = isset($composerInstalled['packages']) ? $composerInstalled['packages'] : $composerInstalled; foreach ($packages as $package) { if (\in_array($package['name'], [self::COMPOSER_PACKAGE_NAME, self::COMPOSER_LEGACY_PACKAGE_NAME], true)) { $this->composerInstallationDetails = $package; break; } } } return $this->composerInstallationDetails; } public function getComposerVersion() { $package = $this->getComposerInstallationDetails(); $versionSuffix = ''; if (isset($package['dist']['reference'])) { $versionSuffix = '#'.$package['dist']['reference']; } return $package['version'].$versionSuffix; } public function getVersion() { if ($this->isInstalledByComposer()) { return Application::VERSION.':'.$this->getComposerVersion(); } return Application::VERSION; } public function isInstalledAsPhar() { return 'phar://' === substr(__DIR__, 0, 7); } public function isInstalledByComposer() { if (null === $this->isInstalledByComposer) { $this->isInstalledByComposer = !$this->isInstalledAsPhar() && file_exists($this->getComposerInstalledFile()); } return $this->isInstalledByComposer; } public function getPharDownloadUri($version) { return sprintf( 'https://github.com/FriendsOfPHP/PHP-CS-Fixer/releases/download/%s/php-cs-fixer.phar', $version ); } private function getComposerInstalledFile() { return __DIR__.'/../../../composer/installed.json'; } } nameValidator = new FixerNameValidator(); } public static function create() { return new self(); } public function setWhitespacesConfig(WhitespacesFixerConfig $config) { foreach ($this->fixers as $fixer) { if ($fixer instanceof WhitespacesAwareFixerInterface) { $fixer->setWhitespacesConfig($config); } } return $this; } public function getFixers() { $this->fixers = Utils::sortFixers($this->fixers); return $this->fixers; } public function registerBuiltInFixers() { static $builtInFixers = null; if (null === $builtInFixers) { $builtInFixers = []; foreach (SymfonyFinder::create()->files()->in(__DIR__.'/Fixer') as $file) { $relativeNamespace = $file->getRelativePath(); $fixerClass = 'PhpCsFixer\\Fixer\\'.($relativeNamespace ? $relativeNamespace.'\\' : '').$file->getBasename('.php'); if ('Fixer' === substr($fixerClass, -5)) { $builtInFixers[] = $fixerClass; } } } foreach ($builtInFixers as $class) { $this->registerFixer(new $class(), false); } return $this; } public function registerCustomFixers(array $fixers) { foreach ($fixers as $fixer) { $this->registerFixer($fixer, true); } return $this; } public function registerFixer(FixerInterface $fixer, $isCustom) { $name = $fixer->getName(); if (isset($this->fixersByName[$name])) { throw new \UnexpectedValueException(sprintf('Fixer named "%s" is already registered.', $name)); } if (!$this->nameValidator->isValid($name, $isCustom)) { throw new \UnexpectedValueException(sprintf('Fixer named "%s" has invalid name.', $name)); } $this->fixers[] = $fixer; $this->fixersByName[$name] = $fixer; return $this; } public function useRuleSet(RuleSetInterface $ruleSet) { $fixers = []; $fixersByName = []; $fixerConflicts = []; $fixerNames = array_keys($ruleSet->getRules()); foreach ($fixerNames as $name) { if (!\array_key_exists($name, $this->fixersByName)) { throw new \UnexpectedValueException(sprintf('Rule "%s" does not exist.', $name)); } $fixer = $this->fixersByName[$name]; $config = $ruleSet->getRuleConfiguration($name); if (null !== $config) { if ($fixer instanceof ConfigurableFixerInterface) { if (!\is_array($config) || !\count($config)) { throw new InvalidFixerConfigurationException($fixer->getName(), 'Configuration must be an array and may not be empty.'); } $fixer->configure($config); } else { throw new InvalidFixerConfigurationException($fixer->getName(), 'Is not configurable.'); } } $fixers[] = $fixer; $fixersByName[$name] = $fixer; $conflicts = array_intersect($this->getFixersConflicts($fixer), $fixerNames); if (\count($conflicts) > 0) { $fixerConflicts[$name] = $conflicts; } } if (\count($fixerConflicts) > 0) { throw new \UnexpectedValueException($this->generateConflictMessage($fixerConflicts)); } $this->fixers = $fixers; $this->fixersByName = $fixersByName; return $this; } public function hasRule($name) { return isset($this->fixersByName[$name]); } private function getFixersConflicts(FixerInterface $fixer) { static $conflictMap = [ 'no_blank_lines_before_namespace' => ['single_blank_line_before_namespace'], ]; $fixerName = $fixer->getName(); return \array_key_exists($fixerName, $conflictMap) ? $conflictMap[$fixerName] : []; } private function generateConflictMessage(array $fixerConflicts) { $message = 'Rule contains conflicting fixers:'; $report = []; foreach ($fixerConflicts as $fixer => $fixers) { $report[$fixer] = array_filter( $fixers, static function ($candidate) use ($report, $fixer) { return !\array_key_exists($candidate, $report) || !\in_array($fixer, $report[$candidate], true); } ); if (\count($report[$fixer]) > 0) { $message .= sprintf("\n- \"%s\" with \"%s\"", $fixer, implode('", "', $report[$fixer])); } } return $message; } } eventDispatcher = $eventDispatcher; $this->cacheManager = $cacheManager; } public function accept() { $file = $this->current(); if (!$file instanceof \SplFileInfo) { throw new \RuntimeException( sprintf( 'Expected instance of "\SplFileInfo", got "%s".', \is_object($file) ? \get_class($file) : \gettype($file) ) ); } $path = $file->isLink() ? $file->getPathname() : $file->getRealPath(); if (isset($this->visitedElements[$path])) { return false; } $this->visitedElements[$path] = true; if (!$file->isFile() || $file->isLink()) { return false; } $content = FileReader::createSingleton()->read($path); if ( '' === $content || !$this->cacheManager->needFixing($file->getPathname(), $content) ) { $this->dispatchEvent( FixerFileProcessedEvent::NAME, new FixerFileProcessedEvent(FixerFileProcessedEvent::STATUS_SKIPPED) ); return false; } return true; } private function dispatchEvent($name, Event $event) { if (null === $this->eventDispatcher) { return; } if ( !$this->eventDispatcher instanceof \Symfony\Contracts\EventDispatcher\EventDispatcherInterface ) { $this->eventDispatcher->dispatch($name, $event); return; } $this->eventDispatcher->dispatch($event, $name); } } linter = $linter; } public function currentLintingResult() { return $this->currentResult; } public function next() { parent::next(); $this->currentResult = $this->valid() ? $this->handleItem($this->current()) : null; } public function rewind() { parent::rewind(); $this->currentResult = $this->valid() ? $this->handleItem($this->current()) : null; } private function handleItem(\SplFileInfo $file) { return $this->linter->lintFile($file->getRealPath()); } } finder = $finder; $this->fixers = $fixers; $this->differ = $differ; $this->eventDispatcher = $eventDispatcher; $this->errorsManager = $errorsManager; $this->linter = $linter; $this->isDryRun = $isDryRun; $this->cacheManager = $cacheManager; $this->directory = $directory ?: new Directory(''); $this->stopOnViolation = $stopOnViolation; } public function fix() { $changed = []; $finder = $this->finder; $finderIterator = $finder instanceof \IteratorAggregate ? $finder->getIterator() : $finder; $fileFilteredFileIterator = new FileFilterIterator( $finderIterator, $this->eventDispatcher, $this->cacheManager ); $collection = $this->linter->isAsync() ? new FileCachingLintingIterator($fileFilteredFileIterator, $this->linter) : new FileLintingIterator($fileFilteredFileIterator, $this->linter); foreach ($collection as $file) { $fixInfo = $this->fixFile($file, $collection->currentLintingResult()); Tokens::clearCache(); if ($fixInfo) { $name = $this->directory->getRelativePathTo($file); $changed[$name] = $fixInfo; if ($this->stopOnViolation) { break; } } } return $changed; } private function fixFile(\SplFileInfo $file, LintingResultInterface $lintingResult) { $name = $file->getPathname(); try { $lintingResult->check(); } catch (LintingException $e) { $this->dispatchEvent( FixerFileProcessedEvent::NAME, new FixerFileProcessedEvent(FixerFileProcessedEvent::STATUS_INVALID) ); $this->errorsManager->report(new Error(Error::TYPE_INVALID, $name, $e)); return; } $old = FileReader::createSingleton()->read($file->getRealPath()); Tokens::setLegacyMode(false); $tokens = Tokens::fromCode($old); $oldHash = $tokens->getCodeHash(); $newHash = $oldHash; $new = $old; $appliedFixers = []; try { foreach ($this->fixers as $fixer) { if ( !$fixer instanceof AbstractFixer && (!$fixer->supports($file) || !$fixer->isCandidate($tokens)) ) { continue; } $fixer->fix($file, $tokens); if ($tokens->isChanged()) { $tokens->clearEmptyTokens(); $tokens->clearChanged(); $appliedFixers[] = $fixer->getName(); } } } catch (\Exception $e) { $this->processException($name, $e); return; } catch (\ParseError $e) { $this->dispatchEvent( FixerFileProcessedEvent::NAME, new FixerFileProcessedEvent(FixerFileProcessedEvent::STATUS_LINT) ); $this->errorsManager->report(new Error(Error::TYPE_LINT, $name, $e)); return; } catch (\Throwable $e) { $this->processException($name, $e); return; } $fixInfo = null; if (!empty($appliedFixers)) { $new = $tokens->generateCode(); $newHash = $tokens->getCodeHash(); } if ($oldHash !== $newHash) { $fixInfo = [ 'appliedFixers' => $appliedFixers, 'diff' => $this->differ->diff($old, $new), ]; try { $this->linter->lintSource($new)->check(); } catch (LintingException $e) { $this->dispatchEvent( FixerFileProcessedEvent::NAME, new FixerFileProcessedEvent(FixerFileProcessedEvent::STATUS_LINT) ); $this->errorsManager->report(new Error(Error::TYPE_LINT, $name, $e, $fixInfo['appliedFixers'], $fixInfo['diff'])); return; } if (!$this->isDryRun) { if (false === @file_put_contents($file->getRealPath(), $new)) { $error = error_get_last(); throw new IOException( sprintf('Failed to write file "%s", "%s".', $file->getPathname(), $error ? $error['message'] : 'no reason available'), 0, null, $file->getRealPath() ); } } } $this->cacheManager->setFile($name, $new); $this->dispatchEvent( FixerFileProcessedEvent::NAME, new FixerFileProcessedEvent($fixInfo ? FixerFileProcessedEvent::STATUS_FIXED : FixerFileProcessedEvent::STATUS_NO_CHANGES) ); return $fixInfo; } private function processException($name, $e) { $this->dispatchEvent( FixerFileProcessedEvent::NAME, new FixerFileProcessedEvent(FixerFileProcessedEvent::STATUS_EXCEPTION) ); $this->errorsManager->report(new Error(Error::TYPE_EXCEPTION, $name, $e)); } private function dispatchEvent($name, Event $event) { if (null === $this->eventDispatcher) { return; } if ( !$this->eventDispatcher instanceof \Symfony\Contracts\EventDispatcher\EventDispatcherInterface ) { $this->eventDispatcher->dispatch($name, $event); return; } $this->eventDispatcher->dispatch($event, $name); } } linter = $linter; } public function currentLintingResult() { return $this->currentResult; } public function next() { parent::next(); $this->currentResult = $this->nextResult; if ($this->hasNext()) { $this->nextResult = $this->handleItem($this->getInnerIterator()->current()); } } public function rewind() { parent::rewind(); if ($this->valid()) { $this->currentResult = $this->handleItem($this->current()); } if ($this->hasNext()) { $this->nextResult = $this->handleItem($this->getInnerIterator()->current()); } } private function handleItem(\SplFileInfo $file) { return $this->linter->lintFile($file->getRealPath()); } } codeSample = new CodeSample($code, $configuration); $this->versionSpecification = $versionSpecification; } public function getCode() { return $this->codeSample->getCode(); } public function getConfiguration() { return $this->codeSample->getConfiguration(); } public function isSuitableFor($version) { return $this->versionSpecification->isSatisfiedBy($version); } } $minimum)) { throw new \InvalidArgumentException('Minimum needs to be either null or an integer greater than 0.'); } if (null !== $maximum) { if (!\is_int($maximum) || 1 > $maximum) { throw new \InvalidArgumentException('Maximum needs to be either null or an integer greater than 0.'); } if (null !== $minimum && $maximum < $minimum) { throw new \InvalidArgumentException('Maximum should not be lower than the minimum.'); } } $this->minimum = $minimum; $this->maximum = $maximum; } public function isSatisfiedBy($version) { if (null !== $this->minimum && $version < $this->minimum) { return false; } if (null !== $this->maximum && $version > $this->maximum) { return false; } return true; } } code = $code; $this->configuration = $configuration; } public function getCode() { return $this->code; } public function getConfiguration() { return $this->configuration; } } codeSample = new CodeSample($code, $configuration); $this->splFileInfo = $splFileInfo; } public function getCode() { return $this->codeSample->getCode(); } public function getConfiguration() { return $this->codeSample->getConfiguration(); } public function getSplFileInfo() { return $this->splFileInfo; } } summary = $summary; $this->codeSamples = $codeSamples; $this->description = $description; $this->configurationDescription = $configurationDescription; $this->defaultConfiguration = $defaultConfiguration; $this->riskyDescription = $riskyDescription; } public function getSummary() { return $this->summary; } public function getDescription() { return $this->description; } public function getConfigurationDescription() { @trigger_error(sprintf('%s is deprecated and will be removed in 3.0.', __METHOD__), E_USER_DEPRECATED); return $this->configurationDescription; } public function getDefaultConfiguration() { @trigger_error(sprintf('%s is deprecated and will be removed in 3.0.', __METHOD__), E_USER_DEPRECATED); return $this->defaultConfiguration; } public function getRiskyDescription() { return $this->riskyDescription; } public function getCodeSamples() { return $this->codeSamples; } } isAnyTokenKindsFound(Token::getClassyTokenKinds()); } public function isRisky() { return true; } public function getPriority() { return -10; } public function supports(\SplFileInfo $file) { if ($file instanceof StdinFileInfo) { return false; } $filenameParts = explode('.', $file->getBasename(), 2); if ( (!isset($filenameParts[1]) || 'php' !== $filenameParts[1]) || 0 === Preg::match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $filenameParts[0]) ) { return false; } try { $tokens = Tokens::fromCode(sprintf('isKeyword() || $tokens[3]->isMagicConstant()) { return false; } } catch (\ParseError $e) { return false; } return !Preg::match('{[/\\\\](stub|fixture)s?[/\\\\]}i', $file->getRealPath()); } } file = $file; } public function getFile() { return $this->file; } public function read() { if (!file_exists($this->file)) { return null; } $content = file_get_contents($this->file); try { $cache = Cache::fromJson($content); } catch (\InvalidArgumentException $exception) { return null; } return $cache; } public function write(CacheInterface $cache) { $content = $cache->toJson(); if (file_exists($this->file)) { if (is_dir($this->file)) { throw new IOException( sprintf('Cannot write cache file "%s" as the location exists as directory.', realpath($this->file)), 0, null, $this->file ); } if (!is_writable($this->file)) { throw new IOException( sprintf('Cannot write to file "%s" as it is not writable.', realpath($this->file)), 0, null, $this->file ); } } else { @touch($this->file); @chmod($this->file, 0666); } $bytesWritten = @file_put_contents($this->file, $content); if (false === $bytesWritten) { $error = error_get_last(); throw new IOException( sprintf('Failed to write file "%s", "%s".', $this->file, isset($error['message']) ? $error['message'] : 'no reason available'), 0, null, $this->file ); } } } handler = $handler; $this->signature = $signature; $this->isDryRun = $isDryRun; $this->cacheDirectory = $cacheDirectory ?: new Directory(''); $this->readCache(); } public function __destruct() { $this->writeCache(); } public function needFixing($file, $fileContent) { $file = $this->cacheDirectory->getRelativePathTo($file); return !$this->cache->has($file) || $this->cache->get($file) !== $this->calcHash($fileContent); } public function setFile($file, $fileContent) { $file = $this->cacheDirectory->getRelativePathTo($file); $hash = $this->calcHash($fileContent); if ($this->isDryRun && $this->cache->has($file) && $this->cache->get($file) !== $hash) { $this->cache->clear($file); return; } $this->cache->set($file, $hash); } private function readCache() { $cache = $this->handler->read(); if (!$cache || !$this->signature->equals($cache->getSignature())) { $cache = new Cache($this->signature); } $this->cache = $cache; } private function writeCache() { $this->handler->write($this->cache); } private function calcHash($content) { return crc32($content); } } directoryName = $directoryName; } public function getRelativePathTo($file) { $file = $this->normalizePath($file); if ( '' === $this->directoryName || 0 !== stripos($file, $this->directoryName.\DIRECTORY_SEPARATOR) ) { return $file; } return substr($file, \strlen($this->directoryName) + 1); } private function normalizePath($path) { return str_replace(['\\', '/'], \DIRECTORY_SEPARATOR, $path); } } phpVersion = $phpVersion; $this->fixerVersion = $fixerVersion; $this->indent = $indent; $this->lineEnding = $lineEnding; $this->rules = self::utf8Encode($rules); } public function getPhpVersion() { return $this->phpVersion; } public function getFixerVersion() { return $this->fixerVersion; } public function getIndent() { return $this->indent; } public function getLineEnding() { return $this->lineEnding; } public function getRules() { return $this->rules; } public function equals(SignatureInterface $signature) { return $this->phpVersion === $signature->getPhpVersion() && $this->fixerVersion === $signature->getFixerVersion() && $this->indent === $signature->getIndent() && $this->lineEnding === $signature->getLineEnding() && $this->rules === $signature->getRules(); } private static function utf8Encode(array $data) { if (!\function_exists('mb_detect_encoding')) { return $data; } array_walk_recursive($data, static function (&$item) { if (\is_string($item) && !mb_detect_encoding($item, 'utf-8', true)) { $item = utf8_encode($item); } }); return $data; } } signature = $signature; } public function getSignature() { return $this->signature; } public function has($file) { return \array_key_exists($file, $this->hashes); } public function get($file) { if (!$this->has($file)) { return null; } return $this->hashes[$file]; } public function set($file, $hash) { $this->hashes[$file] = $hash; } public function clear($file) { unset($this->hashes[$file]); } public function toJson() { $json = json_encode([ 'php' => $this->getSignature()->getPhpVersion(), 'version' => $this->getSignature()->getFixerVersion(), 'indent' => $this->getSignature()->getIndent(), 'lineEnding' => $this->getSignature()->getLineEnding(), 'rules' => $this->getSignature()->getRules(), 'hashes' => $this->hashes, ]); if (JSON_ERROR_NONE !== json_last_error()) { throw new \UnexpectedValueException(sprintf( 'Can not encode cache signature to JSON, error: "%s". If you have non-UTF8 chars in your signature, like in license for `header_comment`, consider enabling `ext-mbstring` or install `symfony/polyfill-mbstring`.', json_last_error_msg() )); } return $json; } public static function fromJson($json) { $data = json_decode($json, true); if (null === $data && JSON_ERROR_NONE !== json_last_error()) { throw new \InvalidArgumentException(sprintf( 'Value needs to be a valid JSON string, got "%s", error: "%s".', $json, json_last_error_msg() )); } $requiredKeys = [ 'php', 'version', 'indent', 'lineEnding', 'rules', 'hashes', ]; $missingKeys = array_diff_key(array_flip($requiredKeys), $data); if (\count($missingKeys)) { throw new \InvalidArgumentException(sprintf( 'JSON data is missing keys "%s"', implode('", "', $missingKeys) )); } $signature = new Signature( $data['php'], $data['version'], $data['indent'], $data['lineEnding'], $data['rules'] ); $cache = new self($signature); $cache->hashes = $data['hashes']; return $cache; } } candidates = $candidates; } public function match($needle) { $word = null; $distance = ceil(\strlen($needle) * 0.35); foreach ($this->candidates as $candidate) { $candidateDistance = levenshtein($needle, $candidate); if ($candidateDistance < $distance) { $word = $candidate; $distance = $candidateDistance; } } return $word; } } isTokenKindFound(T_DOC_COMMENT); } protected function applyFix(\SplFileInfo $file, PhpTokens $phpTokens) { $analyzer = new TokensAnalyzer($phpTokens); $this->classyElements = $analyzer->getClassyElements(); foreach ($phpTokens->findGivenKind(T_DOC_COMMENT) as $index => $docCommentToken) { if (!$this->nextElementAcceptsDoctrineAnnotations($phpTokens, $index)) { continue; } $tokens = DoctrineAnnotationTokens::createFromDocComment( $docCommentToken, $this->configuration['ignored_tags'] ); $this->fixAnnotations($tokens); $phpTokens[$index] = new PhpToken([T_DOC_COMMENT, $tokens->getCode()]); } } abstract protected function fixAnnotations(DoctrineAnnotationTokens $doctrineAnnotationTokens); protected function createConfigurationDefinition() { return new FixerConfigurationResolver([ (new FixerOptionBuilder('ignored_tags', 'List of tags that must not be treated as Doctrine Annotations.')) ->setAllowedTypes(['array']) ->setAllowedValues([static function ($values) { foreach ($values as $value) { if (!\is_string($value)) { return false; } } return true; }]) ->setDefault([ 'abstract', 'access', 'code', 'deprec', 'encode', 'exception', 'final', 'ingroup', 'inheritdoc', 'inheritDoc', 'magic', 'name', 'toc', 'tutorial', 'private', 'static', 'staticvar', 'staticVar', 'throw', 'api', 'author', 'category', 'copyright', 'deprecated', 'example', 'filesource', 'global', 'ignore', 'internal', 'license', 'link', 'method', 'package', 'param', 'property', 'property-read', 'property-write', 'return', 'see', 'since', 'source', 'subpackage', 'throws', 'todo', 'TODO', 'usedBy', 'uses', 'var', 'version', 'after', 'afterClass', 'backupGlobals', 'backupStaticAttributes', 'before', 'beforeClass', 'codeCoverageIgnore', 'codeCoverageIgnoreStart', 'codeCoverageIgnoreEnd', 'covers', 'coversDefaultClass', 'coversNothing', 'dataProvider', 'depends', 'expectedException', 'expectedExceptionCode', 'expectedExceptionMessage', 'expectedExceptionMessageRegExp', 'group', 'large', 'medium', 'preserveGlobalState', 'requires', 'runTestsInSeparateProcesses', 'runInSeparateProcess', 'small', 'test', 'testdox', 'ticket', 'uses', 'SuppressWarnings', 'noinspection', 'package_version', 'enduml', 'startuml', 'fix', 'FIXME', 'fixme', 'override', ]) ->getOption(), ]); } private function nextElementAcceptsDoctrineAnnotations(PhpTokens $tokens, $index) { do { $index = $tokens->getNextMeaningfulToken($index); if (null === $index) { return false; } } while ($tokens[$index]->isGivenKind([T_ABSTRACT, T_FINAL])); if ($tokens[$index]->isClassy()) { return true; } while ($tokens[$index]->isGivenKind([T_PUBLIC, T_PROTECTED, T_PRIVATE, T_FINAL, T_ABSTRACT])) { $index = $tokens->getNextMeaningfulToken($index); } return isset($this->classyElements[$index]); } } = 0; }; $negative = function ($item) { return $item < 0; }; ', ['allow_single_line_closure' => true] ), new CodeSample( ' self::LINE_SAME] ), ] ); } public function getPriority() { return -25; } public function isCandidate(Tokens $tokens) { return true; } protected function applyFix(\SplFileInfo $file, Tokens $tokens) { $this->fixCommentBeforeBrace($tokens); $this->fixMissingControlBraces($tokens); $this->fixIndents($tokens); $this->fixControlContinuationBraces($tokens); $this->fixSpaceAroundToken($tokens); $this->fixDoWhile($tokens); } protected function createConfigurationDefinition() { return new FixerConfigurationResolver([ (new FixerOptionBuilder('allow_single_line_closure', 'Whether single line lambda notation should be allowed.')) ->setAllowedTypes(['bool']) ->setDefault(false) ->getOption(), (new FixerOptionBuilder('position_after_functions_and_oop_constructs', 'whether the opening brace should be placed on "next" or "same" line after classy constructs (non-anonymous classes, interfaces, traits, methods and non-lambda functions).')) ->setAllowedValues([self::LINE_NEXT, self::LINE_SAME]) ->setDefault(self::LINE_NEXT) ->getOption(), (new FixerOptionBuilder('position_after_control_structures', 'whether the opening brace should be placed on "next" or "same" line after control structures.')) ->setAllowedValues([self::LINE_NEXT, self::LINE_SAME]) ->setDefault(self::LINE_SAME) ->getOption(), (new FixerOptionBuilder('position_after_anonymous_constructs', 'whether the opening brace should be placed on "next" or "same" line after anonymous constructs (anonymous classes and lambda functions).')) ->setAllowedValues([self::LINE_NEXT, self::LINE_SAME]) ->setDefault(self::LINE_SAME) ->getOption(), ]); } private function fixCommentBeforeBrace(Tokens $tokens) { $tokensAnalyzer = new TokensAnalyzer($tokens); $controlTokens = $this->getControlTokens(); for ($index = $tokens->count() - 1; 0 <= $index; --$index) { $token = $tokens[$index]; if ($token->isGivenKind($controlTokens)) { $prevIndex = $this->findParenthesisEnd($tokens, $index); } elseif ( ($token->isGivenKind(T_FUNCTION) && $tokensAnalyzer->isLambda($index)) || ($token->isGivenKind(T_CLASS) && $tokensAnalyzer->isAnonymousClass($index)) ) { $prevIndex = $tokens->getNextTokenOfKind($index, ['{']); $prevIndex = $tokens->getPrevMeaningfulToken($prevIndex); } else { continue; } $commentIndex = $tokens->getNextNonWhitespace($prevIndex); $commentToken = $tokens[$commentIndex]; if (!$commentToken->isGivenKind(T_COMMENT) || 0 === strpos($commentToken->getContent(), '/*')) { continue; } $braceIndex = $tokens->getNextMeaningfulToken($commentIndex); $braceToken = $tokens[$braceIndex]; if (!$braceToken->equals('{')) { continue; } $tokenTmp = $tokens[$braceIndex]; $newBraceIndex = $prevIndex + 1; for ($i = $braceIndex; $i > $newBraceIndex; --$i) { $previousToken = $tokens[$i - 1]; $tokens[$i] = $previousToken; if ($tokens[$i]->isWhitespace() && $tokens[$i + 1]->isWhitespace()) { $tokens[$i] = new Token([T_WHITESPACE, $tokens[$i]->getContent().$tokens[$i + 1]->getContent()]); $tokens->clearAt($i + 1); } } $tokens[$newBraceIndex] = $tokenTmp; $c = $tokens[$braceIndex]->getContent(); if (substr_count($c, "\n") > 1) { $tokens[$braceIndex] = new Token([T_WHITESPACE, substr($c, strrpos($c, "\n"))]); } } } private function fixControlContinuationBraces(Tokens $tokens) { $controlContinuationTokens = $this->getControlContinuationTokens(); for ($index = \count($tokens) - 1; 0 <= $index; --$index) { $token = $tokens[$index]; if (!$token->isGivenKind($controlContinuationTokens)) { continue; } $prevIndex = $tokens->getPrevNonWhitespace($index); $prevToken = $tokens[$prevIndex]; if (!$prevToken->equals('}')) { continue; } $tokens->ensureWhitespaceAtIndex( $index - 1, 1, self::LINE_NEXT === $this->configuration['position_after_control_structures'] ? $this->whitespacesConfig->getLineEnding().$this->detectIndent($tokens, $index) : ' ' ); } } private function fixDoWhile(Tokens $tokens) { for ($index = \count($tokens) - 1; 0 <= $index; --$index) { $token = $tokens[$index]; if (!$token->isGivenKind(T_DO)) { continue; } $parenthesisEndIndex = $this->findParenthesisEnd($tokens, $index); $startBraceIndex = $tokens->getNextNonWhitespace($parenthesisEndIndex); $endBraceIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $startBraceIndex); $nextNonWhitespaceIndex = $tokens->getNextNonWhitespace($endBraceIndex); $nextNonWhitespaceToken = $tokens[$nextNonWhitespaceIndex]; if (!$nextNonWhitespaceToken->isGivenKind(T_WHILE)) { continue; } $tokens->ensureWhitespaceAtIndex($nextNonWhitespaceIndex - 1, 1, ' '); } } private function fixIndents(Tokens $tokens) { $classyTokens = Token::getClassyTokenKinds(); $classyAndFunctionTokens = array_merge([T_FUNCTION], $classyTokens); $controlTokens = $this->getControlTokens(); $indentTokens = array_filter( array_merge($classyAndFunctionTokens, $controlTokens), static function ($item) { return T_SWITCH !== $item; } ); $tokensAnalyzer = new TokensAnalyzer($tokens); for ($index = 0, $limit = \count($tokens); $index < $limit; ++$index) { $token = $tokens[$index]; if (!$token->isGivenKind($indentTokens)) { continue; } if ( $token->isGivenKind(T_WHILE) && $tokensAnalyzer->isWhilePartOfDoWhile($index) ) { continue; } if ( $this->configuration['allow_single_line_closure'] && $token->isGivenKind(T_FUNCTION) && $tokensAnalyzer->isLambda($index) ) { $braceEndIndex = $tokens->findBlockEnd( Tokens::BLOCK_TYPE_CURLY_BRACE, $tokens->getNextTokenOfKind($index, ['{']) ); if (!$this->isMultilined($tokens, $index, $braceEndIndex)) { $index = $braceEndIndex; continue; } } if ($token->isGivenKind($classyAndFunctionTokens)) { $startBraceIndex = $tokens->getNextTokenOfKind($index, [';', '{']); $startBraceToken = $tokens[$startBraceIndex]; } else { $parenthesisEndIndex = $this->findParenthesisEnd($tokens, $index); $startBraceIndex = $tokens->getNextNonWhitespace($parenthesisEndIndex); $startBraceToken = $tokens[$startBraceIndex]; } if (!$startBraceToken->equals('{')) { continue; } $nextNonWhitespaceIndex = $tokens->getNextNonWhitespace($startBraceIndex, " \t"); $nextNonWhitespace = $tokens[$nextNonWhitespaceIndex]; if ($nextNonWhitespace->isGivenKind(T_CLOSE_TAG)) { continue; } if ($nextNonWhitespace->isComment() && $tokens[$tokens->getNextMeaningfulToken($nextNonWhitespaceIndex)]->isGivenKind(T_CLOSE_TAG)) { continue; } $endBraceIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $startBraceIndex); $indent = $this->detectIndent($tokens, $index); $tokens->ensureWhitespaceAtIndex($endBraceIndex - 1, 1, $this->whitespacesConfig->getLineEnding().$indent); $lastCommaIndex = $tokens->getPrevTokenOfKind($endBraceIndex - 1, [';', '}']); $nestLevel = 1; for ($nestIndex = $lastCommaIndex; $nestIndex >= $startBraceIndex; --$nestIndex) { $nestToken = $tokens[$nestIndex]; if ($nestToken->equalsAny([')', [CT::T_BRACE_CLASS_INSTANTIATION_CLOSE]])) { $nestIndex = $tokens->findBlockStart( $nestToken->equals(')') ? Tokens::BLOCK_TYPE_PARENTHESIS_BRACE : Tokens::BLOCK_TYPE_BRACE_CLASS_INSTANTIATION, $nestIndex ); continue; } if (1 === $nestLevel) { $nextLineCanBeIndented = false; if ($nestToken->equalsAny([';', '}'])) { $nextLineCanBeIndented = true; } elseif ($this->isCommentWithFixableIndentation($tokens, $nestIndex)) { for ($i = $nestIndex; $i > $startBraceIndex; --$i) { if ($tokens[$i]->equalsAny([';', '}'])) { $nextLineCanBeIndented = true; break; } if (!$tokens[$i]->isWhitespace() && !$tokens[$i]->isComment()) { break; } } if ($nextLineCanBeIndented || $i === $startBraceIndex) { $nextToken = $tokens[$nestIndex + 1]; $nextLineCanBeIndented = $nextToken->isWhitespace() && 1 === Preg::match('/\R/', $nextToken->getContent()); } } if (!$nextLineCanBeIndented) { continue; } $nextNonWhitespaceNestIndex = $tokens->getNextNonWhitespace($nestIndex); $nextNonWhitespaceNestToken = $tokens[$nextNonWhitespaceNestIndex]; if ( !($nextNonWhitespaceNestToken->isComment() && ( !$tokens[$nextNonWhitespaceNestIndex - 1]->isWhitespace() || !Preg::match('/\R/', $tokens[$nextNonWhitespaceNestIndex - 1]->getContent()) )) && !($nestToken->equals('}') && $nextNonWhitespaceNestToken->equalsAny([';', ',', ']', [CT::T_ARRAY_SQUARE_BRACE_CLOSE]])) && !($nestToken->equals('}') && $nextNonWhitespaceNestToken->equals('(')) && !($nestToken->equals('}') && $tokens[$nestIndex - 1]->equalsAny(['"', "'", [T_CONSTANT_ENCAPSED_STRING], [T_VARIABLE]])) && !($tokens[$nestIndex - 1]->isGivenKind(T_END_HEREDOC) && $nextNonWhitespaceNestToken->isGivenKind(T_CLOSE_TAG)) ) { if ( ( self::LINE_NEXT !== $this->configuration['position_after_control_structures'] && $nextNonWhitespaceNestToken->isGivenKind($this->getControlContinuationTokens()) && !$tokens[$tokens->getPrevNonWhitespace($nextNonWhitespaceNestIndex)]->isComment() ) || $nextNonWhitespaceNestToken->isGivenKind(T_CLOSE_TAG) || ( self::LINE_NEXT !== $this->configuration['position_after_control_structures'] && $nextNonWhitespaceNestToken->isGivenKind(T_WHILE) && $tokensAnalyzer->isWhilePartOfDoWhile($nextNonWhitespaceNestIndex) ) ) { $whitespace = ' '; } else { $nextToken = $tokens[$nestIndex + 1]; $nextWhitespace = ''; if ($nextToken->isWhitespace()) { $nextWhitespace = rtrim($nextToken->getContent(), " \t"); if ('' !== $nextWhitespace) { $nextWhitespace = Preg::replace( sprintf('/%s$/', $this->whitespacesConfig->getLineEnding()), '', $nextWhitespace, 1 ); } } $whitespace = $nextWhitespace.$this->whitespacesConfig->getLineEnding().$indent; if (!$nextNonWhitespaceNestToken->equals('}')) { $determineIsIndentableBlockContent = static function ($contentIndex) use ($tokens) { if (!$tokens[$contentIndex]->isComment()) { return true; } if (!$tokens[$tokens->getPrevMeaningfulToken($contentIndex)]->equals(';')) { return true; } $nextIndex = $tokens->getNextMeaningfulToken($contentIndex); if (!$tokens[$nextIndex]->equals('}')) { return true; } $nextNextIndex = $tokens->getNextMeaningfulToken($nextIndex); if (null === $nextNextIndex) { return true; } if ($tokens[$nextNextIndex]->equalsAny([ [T_ELSE], [T_ELSEIF], ',', ])) { return false; } return true; }; if ($determineIsIndentableBlockContent($nestIndex + 2)) { $whitespace .= $this->whitespacesConfig->getIndent(); } } } $this->ensureWhitespaceAtIndexAndIndentMultilineComment($tokens, $nestIndex + 1, $whitespace); } } if ($nestToken->equals('}')) { ++$nestLevel; continue; } if ($nestToken->equals('{')) { --$nestLevel; continue; } } if (isset($tokens[$startBraceIndex + 2]) && $tokens[$startBraceIndex + 2]->equals('}')) { $tokens->ensureWhitespaceAtIndex($startBraceIndex + 1, 0, $this->whitespacesConfig->getLineEnding().$indent); } else { $nextToken = $tokens[$startBraceIndex + 1]; $nextNonWhitespaceToken = $tokens[$tokens->getNextNonWhitespace($startBraceIndex)]; if ( !$nextNonWhitespaceToken->isComment() || ($nextToken->isWhitespace() && 1 === substr_count($nextToken->getContent(), "\n")) ) { $this->ensureWhitespaceAtIndexAndIndentMultilineComment( $tokens, $startBraceIndex + 1, $this->whitespacesConfig->getLineEnding().$indent.$this->whitespacesConfig->getIndent() ); } } if ($token->isGivenKind($classyTokens) && !$tokensAnalyzer->isAnonymousClass($index)) { if (self::LINE_SAME === $this->configuration['position_after_functions_and_oop_constructs'] && !$tokens[$tokens->getPrevNonWhitespace($startBraceIndex)]->isComment()) { $ensuredWhitespace = ' '; } else { $ensuredWhitespace = $this->whitespacesConfig->getLineEnding().$indent; } $tokens->ensureWhitespaceAtIndex($startBraceIndex - 1, 1, $ensuredWhitespace); } elseif ( $token->isGivenKind(T_FUNCTION) && !$tokensAnalyzer->isLambda($index) || ( self::LINE_NEXT === $this->configuration['position_after_control_structures'] && $token->isGivenKind($controlTokens) || ( self::LINE_NEXT === $this->configuration['position_after_anonymous_constructs'] && ( $token->isGivenKind(T_FUNCTION) && $tokensAnalyzer->isLambda($index) || $token->isGivenKind(T_CLASS) && $tokensAnalyzer->isAnonymousClass($index) ) ) ) ) { $isAnonymousClass = $token->isGivenKind($classyTokens) && $tokensAnalyzer->isAnonymousClass($index); $closingParenthesisIndex = $tokens->getPrevTokenOfKind($startBraceIndex, [')']); if (null === $closingParenthesisIndex && !$isAnonymousClass) { continue; } if ( !$isAnonymousClass && $tokens[$closingParenthesisIndex - 1]->isWhitespace() && false !== strpos($tokens[$closingParenthesisIndex - 1]->getContent(), "\n") ) { if (!$tokens[$startBraceIndex - 2]->isComment()) { $tokens->ensureWhitespaceAtIndex($startBraceIndex - 1, 1, ' '); } } else { if ( self::LINE_SAME === $this->configuration['position_after_functions_and_oop_constructs'] && ( $token->isGivenKind(T_FUNCTION) && !$tokensAnalyzer->isLambda($index) || $token->isGivenKind($classyTokens) && !$tokensAnalyzer->isAnonymousClass($index) ) && !$tokens[$tokens->getPrevNonWhitespace($startBraceIndex)]->isComment() ) { $ensuredWhitespace = ' '; } else { $ensuredWhitespace = $this->whitespacesConfig->getLineEnding().$indent; } $tokens->ensureWhitespaceAtIndex($startBraceIndex - 1, 1, $ensuredWhitespace); } } else { $tokens->ensureWhitespaceAtIndex($startBraceIndex - 1, 1, ' '); } $limit = \count($tokens); } } private function fixMissingControlBraces(Tokens $tokens) { $controlTokens = $this->getControlTokens(); for ($index = $tokens->count() - 1; 0 <= $index; --$index) { $token = $tokens[$index]; if (!$token->isGivenKind($controlTokens)) { continue; } $parenthesisEndIndex = $this->findParenthesisEnd($tokens, $index); $nextAfterParenthesisEndIndex = $tokens->getNextMeaningfulToken($parenthesisEndIndex); $tokenAfterParenthesis = $tokens[$nextAfterParenthesisEndIndex]; if ($tokenAfterParenthesis->equals('{') && self::LINE_SAME === $this->configuration['position_after_control_structures']) { $tokens->ensureWhitespaceAtIndex($parenthesisEndIndex + 1, 0, ' '); continue; } if ($tokenAfterParenthesis->equalsAny([';', '{', ':'])) { continue; } if ($tokenAfterParenthesis->isGivenKind([T_FOR, T_FOREACH, T_SWITCH, T_WHILE])) { $tokenAfterParenthesisBlockEnd = $tokens->findBlockEnd( Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $tokens->getNextMeaningfulToken($nextAfterParenthesisEndIndex) ); if ($tokens[$tokens->getNextMeaningfulToken($tokenAfterParenthesisBlockEnd)]->equals(':')) { continue; } } $statementEndIndex = $this->findStatementEnd($tokens, $parenthesisEndIndex); $tokens->insertAt($statementEndIndex + 1, [new Token([T_WHITESPACE, ' ']), new Token('}')]); if (!$tokens[$statementEndIndex]->equalsAny([';', '}'])) { $tokens->insertAt($statementEndIndex + 1, new Token(';')); } $tokens->insertAt($parenthesisEndIndex + 1, new Token('{')); $tokens->ensureWhitespaceAtIndex($parenthesisEndIndex + 1, 0, ' '); } } private function fixSpaceAroundToken(Tokens $tokens) { $controlTokens = $this->getControlTokens(); for ($index = $tokens->count() - 1; 0 <= $index; --$index) { $token = $tokens[$index]; if ($token->isGivenKind(T_DECLARE)) { $this->fixDeclareStatement($tokens, $index); } elseif ($token->isGivenKind($controlTokens) || $token->isGivenKind(CT::T_USE_LAMBDA)) { $nextNonWhitespaceIndex = $tokens->getNextNonWhitespace($index); if (!$tokens[$nextNonWhitespaceIndex]->equals(':')) { $tokens->ensureWhitespaceAtIndex( $index + 1, 0, self::LINE_NEXT === $this->configuration['position_after_control_structures'] && !$tokens[$nextNonWhitespaceIndex]->equals('(') ? $this->whitespacesConfig->getLineEnding().$this->detectIndent($tokens, $index) : ' ' ); } $prevToken = $tokens[$index - 1]; if (!$prevToken->isWhitespace() && !$prevToken->isComment() && !$prevToken->isGivenKind(T_OPEN_TAG)) { $tokens->ensureWhitespaceAtIndex($index - 1, 1, ' '); } } } } private function detectIndent(Tokens $tokens, $index) { while (true) { $whitespaceIndex = $tokens->getPrevTokenOfKind($index, [[T_WHITESPACE]]); if (null === $whitespaceIndex) { return ''; } $whitespaceToken = $tokens[$whitespaceIndex]; if (false !== strpos($whitespaceToken->getContent(), "\n")) { break; } $prevToken = $tokens[$whitespaceIndex - 1]; if ($prevToken->isGivenKind([T_OPEN_TAG, T_COMMENT]) && "\n" === substr($prevToken->getContent(), -1)) { break; } $index = $whitespaceIndex; } $explodedContent = explode("\n", $whitespaceToken->getContent()); return end($explodedContent); } private function findParenthesisEnd(Tokens $tokens, $structureTokenIndex) { $nextIndex = $tokens->getNextMeaningfulToken($structureTokenIndex); $nextToken = $tokens[$nextIndex]; if (!$nextToken->equals('(')) { return $structureTokenIndex; } return $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $nextIndex); } private function findStatementEnd(Tokens $tokens, $parenthesisEndIndex) { $nextIndex = $tokens->getNextMeaningfulToken($parenthesisEndIndex); $nextToken = $tokens[$nextIndex]; if (!$nextToken) { return $parenthesisEndIndex; } if ($nextToken->equals('{')) { return $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $nextIndex); } if ($nextToken->isGivenKind($this->getControlTokens())) { $parenthesisEndIndex = $this->findParenthesisEnd($tokens, $nextIndex); $endIndex = $this->findStatementEnd($tokens, $parenthesisEndIndex); if ($nextToken->isGivenKind([T_IF, T_TRY, T_DO])) { $openingTokenKind = $nextToken->getId(); while (true) { $nextIndex = $tokens->getNextMeaningfulToken($endIndex); $nextToken = isset($nextIndex) ? $tokens[$nextIndex] : null; if ($nextToken && $nextToken->isGivenKind($this->getControlContinuationTokensForOpeningToken($openingTokenKind))) { $parenthesisEndIndex = $this->findParenthesisEnd($tokens, $nextIndex); $endIndex = $this->findStatementEnd($tokens, $parenthesisEndIndex); if ($nextToken->isGivenKind($this->getFinalControlContinuationTokensForOpeningToken($openingTokenKind))) { return $endIndex; } } else { break; } } } return $endIndex; } $index = $parenthesisEndIndex; while (true) { $token = $tokens[++$index]; if ($token->equals('{')) { $index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $index); continue; } if ($token->equals(';')) { return $index; } if ($token->isGivenKind(T_CLOSE_TAG)) { return $tokens->getPrevNonWhitespace($index); } } } private function getControlTokens() { static $tokens = [ T_DECLARE, T_DO, T_ELSE, T_ELSEIF, T_FINALLY, T_FOR, T_FOREACH, T_IF, T_WHILE, T_TRY, T_CATCH, T_SWITCH, ]; return $tokens; } private function getControlContinuationTokens() { static $tokens = [ T_CATCH, T_ELSE, T_ELSEIF, T_FINALLY, ]; retu