Files
icehrm/core/robo/robo.phar
2018-04-29 17:46:42 +02:00

79066 lines
2.2 MiB
Executable File
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env php
<?php
Phar::mapPhar();
/**
* if we're running from phar load the phar autoload,
* else let the script 'robo' search for the autoloader
*/
if (strpos(basename(__FILE__), 'phar')) {
require_once 'phar://robo.phar/vendor/autoload.php';
} else {
if (file_exists(__DIR__.'/vendor/autoload.php')) {
require_once __DIR__.'/vendor/autoload.php';
} elseif (file_exists(__DIR__.'/../../autoload.php')) {
require_once __DIR__ . '/../../autoload.php';
} else {
require_once 'phar://robo.phar/vendor/autoload.php';
}
}
$runner = new \Robo\Runner();
$statusCode = $runner->execute($_SERVER['argv']);
exit($statusCode);
__HALT_COMPILER(); ?>
¶ºU robo.pharsrc/Application.php”a+X”œ<><C593>src/Collection/CallableTask.php1a+X1qŸ¶src/Collection/Collection.php?Qa+X?Qø±ê¶$src/Collection/CollectionBuilder.phpŽ:a+XŽ:<00>Þ&src/Collection/CollectionInterface.phpa+XÕxj(src/Collection/CollectionProcessHook.phpoa+Xo$src/Collection/CompletionWrapper.phpÓ a+XÓ JkÔ¶src/Collection/Element.php¡ a+X¡ ²È˜¶src/Collection/loadTasks.phpUa+XUøZ —¶,src/Collection/NestedCollectionInterface.phpOa+XO±¶ö#¶src/Collection/TaskForEach.php"a+X"ÆkYß¶src/Collection/Temporary.php¥a+X¥aDÁM¶ src/Common/BuilderAwareTrait.phpxa+Xx<00>£ø¶src/Common/CommandArguments.phpp
a+Xp
U&Me¶src/Common/CommandReceiver.phpÿa+Xÿ·SXsrc/Common/ConfigAwareTrait.php¦a+X¦vÝÀŒ¶src/Common/DynamicParams.php.a+X.)&ésrc/Common/ExecCommand.phpAa+XA¸Bsrc/Common/ExecOneCommand.phpæa+XæÖ –Á¶src/Common/InflectionTrait.phpºa+Xºúâªsrc/Common/InputAwareTrait.php a+X eÆåç¶src/Common/IO.phpca+Xcx $src/Common/OutputAwareTrait.phpa+X5Ý^ƒ¶ src/Common/ProgressIndicator.php*a+X*/ß^Õ¶*src/Common/ProgressIndicatorAwareTrait.php±
a+X±
iøh<¶'src/Common/ResourceExistenceChecker.php a+X þN/ô¶src/Common/TaskIO.phpÿa+Xÿø¾€¶src/Common/TimeKeeper.phpùa+XùŽ«kÁ¶src/Common/Timer.phpƒa+Xƒƒ¾\src/Config.phpp
a+Xp
ªsVò¶&src/Contract/BuilderAwareInterface.php¨a+X¨àA¡!src/Contract/CommandInterface.php½a+X½̧“ñ¶$src/Contract/CompletionInterface.phpza+Xz\Ôʹ¶%src/Contract/ConfigAwareInterface.phppa+Xp*'?0¶$src/Contract/InflectionInterface.phpHa+XHs}áe¶!src/Contract/IOAwareInterface.phpêa+Xêâ~%޶%src/Contract/OutputAwareInterface.php{a+X{
[jo¶!src/Contract/PrintedInterface.phpîa+Xî 4^¶0src/Contract/ProgressIndicatorAwareInterface.phpSa+XSˆÆ£"src/Contract/ProgressInterface.php+a+X+ë9Á¶"src/Contract/RollbackInterface.php“a+X“µ[¿#src/Contract/SimulatedInterface.php¡a+X¡40š¶src/Contract/TaskInterface.php'a+X'U_£¶%src/Contract/WrappedTaskInterface.phpµa+Xµ.óĶsrc/Exception/TaskException.phpa+X´<C3B9>#src/Exception/TaskExitException.php3a+X3»·C¶"src/GlobalOptionsEventListener.php©a+X©ÈnÐz¶src/LoadAllTasks.phpa+X¥JÚˆsrc/Log/ResultPrinter.php  a+X  \ç{¤¶src/Log/RoboLogger.phpÌa+XÌÚ4pY¶src/Log/RoboLogLevel.phpía+Xí-éâÙ¶src/Log/RoboLogStyle.phpÜa+XÜI×Cý¶src/Result.php+a+X+]ع¶src/ResultData.php` a+X` dQ<¶ src/Robo.php'.a+X'. "¹¶src/Runner.phpº5a+Xº5þJÚj¶src/Task/ApiGen/ApiGen.php{-a+X{-’¾½:¶src/Task/ApiGen/loadTasks.phpa+XeT䜶src/Task/Archive/Extract.php£#a+X£#! fˆsrc/Task/Archive/loadTasks.phpža+Xž†ŽÈ!¶src/Task/Archive/Pack.phpf a+Xf D‰ü#src/Task/Assets/CssPreprocessor.phpÇa+Xljzsrc/Task/Assets/ImageMinify.phpÂSa+XÂS ƒ¶src/Task/Assets/Less.php{ a+X{ .c3]¶src/Task/Assets/loadTasks.phpLa+XL¯o.(¶src/Task/Assets/Minify.phpoa+XoÞW9ö¶src/Task/Assets/Scss.phpT a+XT Ý„<C39D>÷¶src/Task/Base/Exec.phpqa+Xqó×src/Task/Base/ExecStack.phpža+Xž©Ÿ3P¶src/Task/Base/loadShortcuts.php3a+X3û;⇶src/Task/Base/loadTasks.phpFa+XFÝ}Á¶src/Task/Base/ParallelExec.phpia+Xi“˜Þʶ src/Task/Base/SymfonyCommand.php$a+X$mošµ¶src/Task/Base/Watch.php¡a+X¡úÄà‚¶src/Task/BaseTask.php„a+X„̼‰¶src/Task/Bower/Base.php|a+X|Þ<>b“¶src/Task/Bower/Install.php¿a+X¿f¨3ß¶src/Task/Bower/loadTasks.phpäa+X䟌5N¶src/Task/Bower/Update.phpwa+Xw0W$ src/Task/CommandStack.phpn
a+Xn
9$src/Task/Composer/Base.php®
a+X®
|?ÌV¶"src/Task/Composer/DumpAutoload.phpha+Xh•>*src/Task/Composer/Install.php(a+X(src/Task/Composer/loadTasks.php½a+X½µ†î¶src/Task/Composer/Remove.phpéa+Xé¡<>óF¶src/Task/Composer/Update.php a+X I<>;š¶src/Task/Composer/Validate.phpªa+XªSŠy<C5A0>"src/Task/Development/Changelog.phpÀa+XÀXmõ€¶,src/Task/Development/GenerateMarkdownDoc.php?Ua+X?Uý%src/Task/Development/GenerateTask.php<68>a+X<>ÏF€/¶src/Task/Development/GitHub.phpS a+XS ¬7ƶ&src/Task/Development/GitHubRelease.php€a+X€ò}¶"src/Task/Development/loadTasks.phpða+XðrbŒ¶$src/Task/Development/OpenBrowser.phpÆa+XÆû­µG¶!src/Task/Development/PackPhar.phpÓa+XÓ"src/Task/Development/PhpServer.php<68>a+X<>% ȶsrc/Task/Development/SemVer.phpÚa+XÚÉÐsrc/Task/Docker/Base.php:a+X:¯N¥Ø¶src/Task/Docker/Build.phpOa+XOs5
src/Task/Docker/Commit.phpÇa+XÇþf¾src/Task/Docker/Exec.php‰a+X‰¸lïJ¶src/Task/Docker/loadTasks.phpGa+XGsrc/Task/Docker/Pull.phpãa+Xã[‹æ²¶src/Task/Docker/Remove.phpça+Xç<00>6ó®¶src/Task/Docker/Result.php=a+X=z»C ¶src/Task/Docker/Run.phpßa+Xßøæ‡¶src/Task/Docker/Start.phpÉa+XÉOwÐͶsrc/Task/Docker/Stop.phpÅa+XÅ ±a÷¶src/Task/File/Concat.phpka+XkW´src/Task/File/loadTasks.phpLa+XL=Yósrc/Task/File/Replace.phpÆ a+XÆ ‘ö‚‚¶src/Task/File/TmpFile.phpÙa+XÙǶsrc/Task/File/Write.phpÑa+XÑÀ)VŶsrc/Task/Filesystem/BaseDir.php1a+X1D€Æ¶ src/Task/Filesystem/CleanDir.php0a+X0%Óõ¶src/Task/Filesystem/CopyDir.phpÅ
a+XÅ
5)Å[¶!src/Task/Filesystem/DeleteDir.phpäa+XäÓ¾^T¶'src/Task/Filesystem/FilesystemStack.phpça+XçÑ~Ô?¶"src/Task/Filesystem/FlattenDir.phpÔ a+XÔ ½õDt¶%src/Task/Filesystem/loadShortcuts.php` a+X` ƒðÖ ¶!src/Task/Filesystem/loadTasks.phpƒa+Xƒ-é•c¶!src/Task/Filesystem/MirrorDir.phpha+XhR>¦Â¶src/Task/Filesystem/TmpDir.php—a+X—ö‰ˆ¶src/Task/Filesystem/WorkDir.phpca+XcPßr¶src/Task/Gulp/Base.phpa+XiÇt̶src/Task/Gulp/loadTasks.phpBa+XB‘è¤È¶src/Task/Gulp/Run.phpNa+XN/_0¶src/Task/Npm/Base.phpba+XbÝ‹src/Task/Npm/Install.phpœa+Xœœ¶…^¶src/Task/Npm/loadTasks.phpða+XðÌ¡ª¶src/Task/Npm/Update.phpTa+XTc<>src/Task/Remote/loadTasks.phpÑa+XÑåäŸ+¶src/Task/Remote/Rsync.php6"a+X6"ä¥/|¶src/Task/Remote/Ssh.php‰a+X‰ÖÈKs¶src/Task/Simulator.php²a+X²0´¾]¶src/Task/StackBasedTask.phpÕa+XÕ9…V¶src/Task/Testing/Atoum.php<68> a+X<> ÇÕÊœ¶src/Task/Testing/Behat.php\ a+X\ ãoضsrc/Task/Testing/Codecept.phpQa+XQ<00>À™€¶src/Task/Testing/loadTasks.phpºa+XºÛõsrc/Task/Testing/Phpspec.phpz
a+Xz
¸src/Task/Testing/PHPUnit.phpTa+XTA!h3¶src/Task/Vcs/GitStack.php a+X ¼4ô-¶src/Task/Vcs/HgStack.phpð a+Xð Ägrsrc/Task/Vcs/loadShortcuts.phpta+Xtðï϶src/Task/Vcs/loadTasks.php4a+X4”Ÿ%ƶsrc/Task/Vcs/SvnStack.phpÊa+XÊš0°—¶src/TaskAccessor.phpa+Xe…src/TaskInfo.phpa+XQ%l¶ src/Tasks.php<a+X<Ðw82¶vendor/autoload.php·a+X·wIe¶%vendor/composer/autoload_classmap.php.a+X. ϲp¶"vendor/composer/autoload_files.phpóa+Xó<õͶ'vendor/composer/autoload_namespaces.phpa+X³Gß¶!vendor/composer/autoload_psr4.phpÊa+XÊdÉè«¶!vendor/composer/autoload_real.phpÔ a+XÔ ‰“2#vendor/composer/autoload_static.phpÂ.a+XÂ.<¹ÐÔ¶vendor/composer/ClassLoader.php²0a+X²0ú<>Ý]¶!vendor/composer/include_paths.php7a+X7Àßʱ¶?vendor/consolidation/annotated-command/src/AnnotatedCommand.php„,a+X„,û:Vó¶Fvendor/consolidation/annotated-command/src/AnnotatedCommandFactory.php¤-a+X¤-$Vö¶=vendor/consolidation/annotated-command/src/AnnotationData.phpua+Xuê9Ovendor/consolidation/annotated-command/src/CommandCreationListenerInterface.php¯a+X¯"\4:vendor/consolidation/annotated-command/src/CommandData.phpæ a+Xæ ÅÆŠ;vendor/consolidation/annotated-command/src/CommandError.phpPa+XP­Œ9¾¶Cvendor/consolidation/annotated-command/src/CommandFileDiscovery.php°.a+X°.þÈ\¸¶Jvendor/consolidation/annotated-command/src/CommandInfoAltererInterface.phpéa+XéÑ<>·”¶?vendor/consolidation/annotated-command/src/CommandProcessor.php©$a+X©$·’µ¶@vendor/consolidation/annotated-command/src/ExitCodeInterface.php a+X ûÖð+¶Ivendor/consolidation/annotated-command/src/Hooks/AlterResultInterface.php#a+X#~<7E>«u¶Kvendor/consolidation/annotated-command/src/Hooks/ExtractOutputInterface.phpha+Xhîȉë¶@vendor/consolidation/annotated-command/src/Hooks/HookManager.phpUa+XUp™ø(¶Lvendor/consolidation/annotated-command/src/Hooks/InitializeHookInterface.php°a+X° VHvendor/consolidation/annotated-command/src/Hooks/InteractorInterface.php:a+X:·Ã<C2B7>ð¶Hvendor/consolidation/annotated-command/src/Hooks/OptionHookInterface.phpŠa+XŠÈük<C3BC>Kvendor/consolidation/annotated-command/src/Hooks/ProcessResultInterface.php<68>a+X<>ê2Un¶Nvendor/consolidation/annotated-command/src/Hooks/StatusDeterminerInterface.php®a+X®Ãèx<¶Gvendor/consolidation/annotated-command/src/Hooks/ValidatorInterface.php#a+X#¿ýwP¶Ovendor/consolidation/annotated-command/src/Options/AlterOptionsCommandEvent.phpñ a+Xñ Þ*Xvendor/consolidation/annotated-command/src/Options/AutomaticOptionsProviderInterface.phpma+Xm<00>I™f¶Bvendor/consolidation/annotated-command/src/OutputDataInterface.php?a+X?<05>¼Avendor/consolidation/annotated-command/src/Parser/CommandInfo.phpß>a+Xß>¹.)¶Nvendor/consolidation/annotated-command/src/Parser/DefaultsWithDescriptions.phpa+Xi…Ç©¶\vendor/consolidation/annotated-command/src/Parser/Internal/AbstractCommandDocBlockParser.phpg a+Xg \Wú¶Uvendor/consolidation/annotated-command/src/Parser/Internal/CommandDocBlockParser2.phpma+Xm¤ñž;¶Uvendor/consolidation/annotated-command/src/Parser/Internal/CommandDocBlockParser3.php a+X ·¶—r¶[vendor/consolidation/annotated-command/src/Parser/Internal/CommandDocBlockParserFactory.php€a+X€p'Ê ¶0vendor/consolidation/log/src/ConsoleLogLevel.phpùa+Xùnô'x¶'vendor/consolidation/log/src/Logger.php$a+X$½®"ý¶0vendor/consolidation/log/src/LogOutputStyler.phpEa+XE®k9vendor/consolidation/log/src/LogOutputStylerInterface.phpGa+XG‡Pt¶7vendor/consolidation/log/src/SymfonyLogOutputStyler.phpa+X2Âö§¶8vendor/consolidation/log/src/UnstyledLogOutputStyler.php a+X Ó=­™¶Tvendor/consolidation/output-formatters/src/Exception/AbstractDataFormatException.phpÜa+XÜ<00>œWP¶Rvendor/consolidation/output-formatters/src/Exception/IncompatibleDataException.phpôa+Xô»åOvendor/consolidation/output-formatters/src/Exception/InvalidFormatException.php9a+X9ôÕø¶Nvendor/consolidation/output-formatters/src/Exception/UnknownFieldException.phpPa+XPÛÖYOvendor/consolidation/output-formatters/src/Exception/UnknownFormatException.phpVa+XV?vendor/consolidation/output-formatters/src/FormatterManager.php˜=a+X˜=é°6ê¶Fvendor/consolidation/output-formatters/src/Formatters/CsvFormatter.php{a+X{ƒ5` ¶Lvendor/consolidation/output-formatters/src/Formatters/FormatterInterface.phpña+XñJ¯?^¶Gvendor/consolidation/output-formatters/src/Formatters/JsonFormatter.phpa+XšxÆ»¶Gvendor/consolidation/output-formatters/src/Formatters/ListFormatter.php&a+X&YÀiɶIvendor/consolidation/output-formatters/src/Formatters/PrintRFormatter.phpâa+Xâ=k<>Mvendor/consolidation/output-formatters/src/Formatters/RenderDataInterface.php>a+X><00>TzÛ¶Nvendor/consolidation/output-formatters/src/Formatters/RenderTableDataTrait.php¥a+X¥.Õ—¶Kvendor/consolidation/output-formatters/src/Formatters/SectionsFormatter.phpÞ a+XÞ •vÞLvendor/consolidation/output-formatters/src/Formatters/SerializeFormatter.phpåa+Xåô¿³¸¶Ivendor/consolidation/output-formatters/src/Formatters/StringFormatter.php<68>
a+X<>
uL¤|¶Hvendor/consolidation/output-formatters/src/Formatters/TableFormatter.php> a+X> (™Fvendor/consolidation/output-formatters/src/Formatters/TsvFormatter.phpa+XëÄ–þ¶Lvendor/consolidation/output-formatters/src/Formatters/VarExportFormatter.phpîa+Xî<00>îÖFvendor/consolidation/output-formatters/src/Formatters/XmlFormatter.php, a+X, d‰ Gvendor/consolidation/output-formatters/src/Formatters/YamlFormatter.phpCa+XCG<>ÄGvendor/consolidation/output-formatters/src/Options/FormatterOptions.phpÏ'a+XÏ'võ¶Ovendor/consolidation/output-formatters/src/Options/OverrideOptionsInterface.phpa+XOkÂt¶Tvendor/consolidation/output-formatters/src/StructuredData/AbstractStructuredList.phpŽ a+XŽ ª‹ž¶Mvendor/consolidation/output-formatters/src/StructuredData/AssociativeList.php®a+X®Ï/¿¶Nvendor/consolidation/output-formatters/src/StructuredData/CallableRenderer.phpJa+XJzº(¶Ovendor/consolidation/output-formatters/src/StructuredData/ListDataInterface.phpÆa+Xƈ'1"¶Svendor/consolidation/output-formatters/src/StructuredData/OriginalDataInterface.phpa+Xð…a§¶Jvendor/consolidation/output-formatters/src/StructuredData/PropertyList.phpáa+Xá2,Ö¶[vendor/consolidation/output-formatters/src/StructuredData/RenderCellCollectionInterface.phpa+X;çÞÛ¶Wvendor/consolidation/output-formatters/src/StructuredData/RenderCellCollectionTrait.phpka+Xk]äöQvendor/consolidation/output-formatters/src/StructuredData/RenderCellInterface.phpÞa+XÞ¬p<}¶Rvendor/consolidation/output-formatters/src/StructuredData/RestructureInterface.php<68>a+X<>¹ðc{¶Jvendor/consolidation/output-formatters/src/StructuredData/RowsOfFields.php™a+X™ÜD)_¶Pvendor/consolidation/output-formatters/src/StructuredData/TableDataInterface.php"a+X"nF<6E>Rvendor/consolidation/output-formatters/src/StructuredData/Xml/DomDataInterface.phpáa+Xá¹Ö²„¶Kvendor/consolidation/output-formatters/src/StructuredData/Xml/XmlSchema.phpì a+Xì Rzp¶Tvendor/consolidation/output-formatters/src/StructuredData/Xml/XmlSchemaInterface.phpa+XMÀ}¶Svendor/consolidation/output-formatters/src/Transformations/DomToArraySimplifier.phpa+Xr#Ù¶[vendor/consolidation/output-formatters/src/Transformations/OverrideRestructureInterface.phpa+X·ñ …¶^vendor/consolidation/output-formatters/src/Transformations/PropertyListTableTransformation.phpùa+Xùc‡¶Mvendor/consolidation/output-formatters/src/Transformations/PropertyParser.php¿a+X¿ƒÙ£s¶Lvendor/consolidation/output-formatters/src/Transformations/ReorderFields.php|a+X|<00>×Wvendor/consolidation/output-formatters/src/Transformations/SimplifyToArrayInterface.phpÅa+XÅ2yé¶Rvendor/consolidation/output-formatters/src/Transformations/TableTransformation.php™ a+X™ ¸€¶Kvendor/consolidation/output-formatters/src/Validate/ValidationInterface.phpa+XžÓU†¶Ovendor/consolidation/output-formatters/src/Validate/ValidDataTypesInterface.phpja+Xj 9P¦¶Kvendor/consolidation/output-formatters/src/Validate/ValidDataTypesTrait.php§a+X§ÎãWvendor/container-interop/container-interop/src/Interop/Container/ContainerInterface.phpåa+Xå7CÜg¶avendor/container-interop/container-interop/src/Interop/Container/Exception/ContainerException.phpýa+XýÚùá ¶`vendor/container-interop/container-interop/src/Interop/Container/Exception/NotFoundException.phpüa+Xü+U²l¶>vendor/henrikbjorn/lurker/src/Lurker/Event/FilesystemEvent.phpY a+XY T½7¶Evendor/henrikbjorn/lurker/src/Lurker/Exception/ExceptionInterface.phpÄa+XÄänHP¶Kvendor/henrikbjorn/lurker/src/Lurker/Exception/InvalidArgumentException.php"a+X"ð÷"¨¶Cvendor/henrikbjorn/lurker/src/Lurker/Exception/RuntimeException.phpúa+Xú…m·á¶Cvendor/henrikbjorn/lurker/src/Lurker/Resource/DirectoryResource.php
a+X
Ø;YZ¶>vendor/henrikbjorn/lurker/src/Lurker/Resource/FileResource.phpxa+Xx5±¾¶Cvendor/henrikbjorn/lurker/src/Lurker/Resource/ResourceInterface.php«a+X«ì&¶7¶Avendor/henrikbjorn/lurker/src/Lurker/Resource/TrackedResource.phpa+X/E8vendor/henrikbjorn/lurker/src/Lurker/ResourceWatcher.php a+X Á
϶Kvendor/henrikbjorn/lurker/src/Lurker/StateChecker/DirectoryStateChecker.phpa+XþXFvendor/henrikbjorn/lurker/src/Lurker/StateChecker/FileStateChecker.php,a+X,%‰Hvendor/henrikbjorn/lurker/src/Lurker/StateChecker/Inotify/CheckerBag.phpa+XsSvendor/henrikbjorn/lurker/src/Lurker/StateChecker/Inotify/DirectoryStateChecker.phpG$a+XG$¯$G3¶Nvendor/henrikbjorn/lurker/src/Lurker/StateChecker/Inotify/FileStateChecker.phpªa+Xª—.EX¶Vvendor/henrikbjorn/lurker/src/Lurker/StateChecker/Inotify/NewDirectoryStateChecker.phpa+X_c×Rvendor/henrikbjorn/lurker/src/Lurker/StateChecker/Inotify/ResourceStateChecker.phpýa+Xýhª@y¶Vvendor/henrikbjorn/lurker/src/Lurker/StateChecker/Inotify/TopDirectoryStateChecker.phpRa+XRvŽO?¶Nvendor/henrikbjorn/lurker/src/Lurker/StateChecker/NewDirectoryStateChecker.phpa+X´+H¶Jvendor/henrikbjorn/lurker/src/Lurker/StateChecker/ResourceStateChecker.phph a+Xh x _Ò¶Kvendor/henrikbjorn/lurker/src/Lurker/StateChecker/StateCheckerInterface.phpÑa+XѤ(.f¶?vendor/henrikbjorn/lurker/src/Lurker/Tracker/InotifyTracker.phpD a+XD ÛØ/}¶Ivendor/henrikbjorn/lurker/src/Lurker/Tracker/RecursiveIteratorTracker.phpºa+XºÉìüp¶Avendor/henrikbjorn/lurker/src/Lurker/Tracker/TrackerInterface.phpŒa+XŒ ПƶBvendor/league/container/src/Argument/ArgumentResolverInterface.phpÏa+XÏt>vendor/league/container/src/Argument/ArgumentResolverTrait.phpAa+XAŽ÷aµ¶4vendor/league/container/src/Argument/RawArgument.phpa+X3—=vendor/league/container/src/Argument/RawArgumentInterface.phpÉa+XÉÓ}ì¶)vendor/league/container/src/Container.phpE a+XE ^O2¶7vendor/league/container/src/ContainerAwareInterface.php€a+X€©Þ¿¶3vendor/league/container/src/ContainerAwareTrait.phpaa+Xa¦SV¶2vendor/league/container/src/ContainerInterface.php\a+X\²µã¶=vendor/league/container/src/Definition/AbstractDefinition.php{a+X{üì6^¶=vendor/league/container/src/Definition/CallableDefinition.php¥a+X¥²áy¶:vendor/league/container/src/Definition/ClassDefinition.php×a+X×Súß»¶Cvendor/league/container/src/Definition/ClassDefinitionInterface.phpèa+Xè›Ó|<vendor/league/container/src/Definition/DefinitionFactory.phpa+XàÁ—ß¶Evendor/league/container/src/Definition/DefinitionFactoryInterface.phpŽa+XŽñO6F¶>vendor/league/container/src/Definition/DefinitionInterface.phpCa+XCB<>;vendor/league/container/src/Exception/NotFoundException.phpa+XCSY¶@vendor/league/container/src/ImmutableContainerAwareInterface.phpâa+Xâ¾@ŒM¶<vendor/league/container/src/ImmutableContainerAwareTrait.phpÄa+XÄygß;vendor/league/container/src/ImmutableContainerInterface.php¹a+X¹+;<3B>3vendor/league/container/src/Inflector/Inflector.php+ a+X+ ÉtK¥¶<vendor/league/container/src/Inflector/InflectorAggregate.php£a+X£vEvendor/league/container/src/Inflector/InflectorAggregateInterface.phpHa+XHá\3vendor/league/container/src/ReflectionContainer.phpÓa+XÓ¸°üê¶Gvendor/league/container/src/ServiceProvider/AbstractServiceProvider.phpîa+XîûPvendor/league/container/src/ServiceProvider/AbstractSignatureServiceProvider.php?a+X?-ûBY¶Pvendor/league/container/src/ServiceProvider/BootableServiceProviderInterface.phpla+Xl=ê¶Hvendor/league/container/src/ServiceProvider/ServiceProviderAggregate.phpœa+Xœáo´Qvendor/league/container/src/ServiceProvider/ServiceProviderAggregateInterface.phpa+XÍœlHvendor/league/container/src/ServiceProvider/ServiceProviderInterface.phpÏa+XÏÞùï·¶Qvendor/league/container/src/ServiceProvider/SignatureServiceProviderInterface.phpla+Xlž„#vendor/natxet/CssMin/src/CssMin.php»ta+X»t.cÃö¶*vendor/patchwork/jsqueeze/src/JSqueeze.php§a+X§#(àŠ¶'vendor/pear/archive_tar/Archive/Tar.phpEa+XE4B:¶-vendor/pear/console_getopt/Console/Getopt.phpd4a+Xd4o6+.vendor/pear/pear-core-minimal/src/OS/Guess.phpe)a+Xe)åšó{¶0vendor/pear/pear-core-minimal/src/PEAR/Error.phpCa+XC×;_ð¶5vendor/pear/pear-core-minimal/src/PEAR/ErrorStack.phpa+X*yYô¶*vendor/pear/pear-core-minimal/src/PEAR.phpŠa+XŠå£ci¶,vendor/pear/pear-core-minimal/src/System.phpKOa+XKO`ƒ¿€¶-vendor/pear/pear_exception/PEAR/Exception.php#<a+X#<ôÁ£¶6vendor/phpdocumentor/reflection-common/src/Element.php1a+X1…iUÒ¶3vendor/phpdocumentor/reflection-common/src/File.php7a+X7ã©3"¶4vendor/phpdocumentor/reflection-common/src/Fqsen.phpa+XC†¼¶7vendor/phpdocumentor/reflection-common/src/Location.phpHa+XH?-ÿ6vendor/phpdocumentor/reflection-common/src/Project.phpa+X/H<>=vendor/phpdocumentor/reflection-common/src/ProjectFactory.phpa+XQ³"ܶWvendor/phpdocumentor/reflection-docblock/examples/01-interpreting-a-simple-docblock.php}a+X}
½´Jvendor/phpdocumentor/reflection-docblock/examples/02-interpreting-tags.phpua+XuRvendor/phpdocumentor/reflection-docblock/examples/03-reconstituting-a-docblock.phpèa+Xèu­Lvendor/phpdocumentor/reflection-docblock/examples/04-adding-your-own-tag.phpPa+XP4w[vendor/phpdocumentor/reflection-docblock/examples/playing-with-descriptions/02-escaping.phpÉa+XÉTDÓȶEvendor/phpdocumentor/reflection-docblock/src/DocBlock/Description.php<68> a+X<> ÉÁ¥Lvendor/phpdocumentor/reflection-docblock/src/DocBlock/DescriptionFactory.phpqa+Xq†:¼¼¶Gvendor/phpdocumentor/reflection-docblock/src/DocBlock/ExampleFinder.phpÉa+XÉŸÒ+¶Dvendor/phpdocumentor/reflection-docblock/src/DocBlock/Serializer.php7a+X7ì@]D¶Lvendor/phpdocumentor/reflection-docblock/src/DocBlock/StandardTagFactory.phpx-a+Xx-ò,=vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tag.phpua+Xuâ¹°Dvendor/phpdocumentor/reflection-docblock/src/DocBlock/TagFactory.phpa+XP;ͶEvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Author.php¸ a+X¸ ãˆtc¶Fvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/BaseTag.php¿a+X¿X<>
Evendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Covers.phpJa+XJýXL¶Ivendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Deprecated.php—
a+X—
HO¶¶Fvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Example.phpQa+XQï­Svendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/StaticMethod.php×a+X׳2i¼¶Ovendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/Strategy.phpÌa+XÌðÞR]vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Formatter/PassthroughFormatter.php%a+X%I`ùá¶Hvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Formatter.php£a+X£Dy7¶Fvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Generic.phpX
a+XX
Cvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Link.phpNa+XNVŒŠ¶Evendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Method.phpŒa+XŒ™ÀoDvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Param.php{a+X{äv¾O¶Gvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Property.phpÓ a+XÓ T€Ï̶Kvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/PropertyRead.phpá a+Xá ÃÑ<C383>ݶLvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/PropertyWrite.phpä a+Xä ç˜íFvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Return_.php„a+X„•¾R¶Bvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/See.phpda+XdOó¶Dvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Since.phpÿ a+Xÿ å¸PEvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Source.phpm a+Xm ˆd%Evendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Throws.php<68>a+X<>èȈضCvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Uses.phpPa+XPT,¾¶Cvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Var_.phpÝ a+XÝ ïLFvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Version.phpÄ a+XÄ dÜ8â¶9vendor/phpdocumentor/reflection-docblock/src/DocBlock.phpša+XšŠ/å‘¶@vendor/phpdocumentor/reflection-docblock/src/DocBlockFactory.php“$a+X“$JlÒ2¶Ivendor/phpdocumentor/reflection-docblock/src/DocBlockFactoryInterface.php!a+X!ئ}¶Ivendor/phpdocumentor/type-resolver/examples/01-resolving-simple-types.phpRa+XRd[Ú~¶Dvendor/phpdocumentor/type-resolver/examples/02-resolving-classes.php¥a+X¥¿þ`ò¶Ivendor/phpdocumentor/type-resolver/examples/03-resolving-all-elements.phpOa+XO~$ØÑ¶avendor/phpdocumentor/type-resolver/examples/04-discovering-the-context-using-class-reflection.phpØa+XØ­Ó¶bvendor/phpdocumentor/type-resolver/examples/05-discovering-the-context-using-method-reflection.phpèa+Xè4 «É¶^vendor/phpdocumentor/type-resolver/examples/06-discovering-the-context-using-file-contents.phpêa+Xê¢}ú¶6vendor/phpdocumentor/type-resolver/examples/Classy.phpÆa+XÆþw<1A>8vendor/phpdocumentor/type-resolver/src/FqsenResolver.phpÂa+XÂ]Y/vendor/phpdocumentor/type-resolver/src/Type.php±a+X±¯Ú[L¶7vendor/phpdocumentor/type-resolver/src/TypeResolver.phpž"a+Xž"W.7vendor/phpdocumentor/type-resolver/src/Types/Array_.phpNa+XN]ɤv¶8vendor/phpdocumentor/type-resolver/src/Types/Boolean.phpÄa+XÄÍfÿ¶:vendor/phpdocumentor/type-resolver/src/Types/Callable_.phpËa+XË4É¿¶9vendor/phpdocumentor/type-resolver/src/Types/Compound.php a+X kd4b¶8vendor/phpdocumentor/type-resolver/src/Types/Context.php] a+X] â?vendor/phpdocumentor/type-resolver/src/Types/ContextFactory.phpôa+Xô‡“;¶7vendor/phpdocumentor/type-resolver/src/Types/Float_.php½a+X½Œw,8vendor/phpdocumentor/type-resolver/src/Types/Integer.phpŽa+XŽ"s‰¶6vendor/phpdocumentor/type-resolver/src/Types/Mixed.phpÏa+XÏ·úZ6vendor/phpdocumentor/type-resolver/src/Types/Null_.phpÈa+XÈ@¥%²¶8vendor/phpdocumentor/type-resolver/src/Types/Object_.phpka+XkÔ©ã¶9vendor/phpdocumentor/type-resolver/src/Types/Resource.phpÎa+XÎàà/¶7vendor/phpdocumentor/type-resolver/src/Types/Scalar.phpa+XUô“»¶6vendor/phpdocumentor/type-resolver/src/Types/Self_.phpa+Xøñ9'¶8vendor/phpdocumentor/type-resolver/src/Types/Static_.phpUa+XU´ÞŸ 8vendor/phpdocumentor/type-resolver/src/Types/String_.phpÉa+XÉ¢½ï¶5vendor/phpdocumentor/type-resolver/src/Types/This.php©a+X©ñh²¶6vendor/phpdocumentor/type-resolver/src/Types/Void_.phpWa+XWÖé¿Ö¶)vendor/psr/log/Psr/Log/AbstractLogger.php a+X šGl¶3vendor/psr/log/Psr/Log/InvalidArgumentException.php`a+X` ˆX1¶/vendor/psr/log/Psr/Log/LoggerAwareInterface.php)a+X)Èj ±¶+vendor/psr/log/Psr/Log/LoggerAwareTrait.php<68>a+X<>z%Ô*vendor/psr/log/Psr/Log/LoggerInterface.phpß a+Xß ?}¶&vendor/psr/log/Psr/Log/LoggerTrait.php a+X ›Ã½õ¶#vendor/psr/log/Psr/Log/LogLevel.phpPa+XP<00>òº%vendor/psr/log/Psr/Log/NullLogger.phpa+XšöZf¶%vendor/symfony/config/ConfigCache.phpÛa+XÛnΡê¶,vendor/symfony/config/ConfigCacheFactory.phpZa+XZŽãXø¶5vendor/symfony/config/ConfigCacheFactoryInterface.phpÎa+XÎaÿ³¶.vendor/symfony/config/ConfigCacheInterface.php0a+X0ú ed¶.vendor/symfony/config/Definition/ArrayNode.phpW,a+XW,#)pl¶-vendor/symfony/config/Definition/BaseNode.phpà!a+Xà!¬P\޶0vendor/symfony/config/Definition/BooleanNode.php©a+X©ÿ_Ê@vendor/symfony/config/Definition/Builder/ArrayNodeDefinition.phpË9a+XË9<00>ÿ=n¶Bvendor/symfony/config/Definition/Builder/BooleanNodeDefinition.phpõa+XõûÞLk¶?vendor/symfony/config/Definition/Builder/EnumNodeDefinition.phpa+Xõ<ŒÖ¶8vendor/symfony/config/Definition/Builder/ExprBuilder.php>a+X>âJȵ¶@vendor/symfony/config/Definition/Builder/FloatNodeDefinition.phpùa+Xùt£p¶Bvendor/symfony/config/Definition/Builder/IntegerNodeDefinition.phpa+X'nÚ­9vendor/symfony/config/Definition/Builder/MergeBuilder.phpva+Xvž&°¶8vendor/symfony/config/Definition/Builder/NodeBuilder.php—a+X—;vendor/symfony/config/Definition/Builder/NodeDefinition.php[a+X[¦€<C2A6>@vendor/symfony/config/Definition/Builder/NodeParentInterface.php¿a+X¿YîÇÓ¶Avendor/symfony/config/Definition/Builder/NormalizationBuilder.php5a+X5@œÅ)¶Bvendor/symfony/config/Definition/Builder/NumericNodeDefinition.phpŸa+XŸí­ë<C2AD>Jvendor/symfony/config/Definition/Builder/ParentNodeDefinitionInterface.php`a+X`8*ÿ=¶Avendor/symfony/config/Definition/Builder/ScalarNodeDefinition.phpÞa+XÞÏYÜ÷¶8vendor/symfony/config/Definition/Builder/TreeBuilder.php<68>a+X<>VÈhU¶>vendor/symfony/config/Definition/Builder/ValidationBuilder.php\a+X\XNI޶Cvendor/symfony/config/Definition/Builder/VariableNodeDefinition.php€a+X€S2·¶;vendor/symfony/config/Definition/ConfigurationInterface.phpea+Xe–¶>vendor/symfony/config/Definition/Dumper/XmlReferenceDumper.php 'a+X 'ü BÒ¶?vendor/symfony/config/Definition/Dumper/YamlReferenceDumper.phpa+XíkÚK¶-vendor/symfony/config/Definition/EnumNode.phpýa+Xý]™13¶Dvendor/symfony/config/Definition/Exception/DuplicateKeyException.phpEa+XEø|÷8vendor/symfony/config/Definition/Exception/Exception.phpËa+X˹po:¶Jvendor/symfony/config/Definition/Exception/ForbiddenOverwriteException.phpQa+XQ¨:2&¶Lvendor/symfony/config/Definition/Exception/InvalidConfigurationException.phpRa+XRh'Nñ¶Ivendor/symfony/config/Definition/Exception/InvalidDefinitionException.phpÚa+XÚ]F4ç¶Cvendor/symfony/config/Definition/Exception/InvalidTypeException.phpía+Xí¾Ä@¶@vendor/symfony/config/Definition/Exception/UnsetKeyException.phpa+Xþ¤½¿¶.vendor/symfony/config/Definition/FloatNode.phpPa+XPÇK”À¶0vendor/symfony/config/Definition/IntegerNode.phpÊa+XÊœ@<40>2vendor/symfony/config/Definition/NodeInterface.phpŠa+Xв÷ˆ0vendor/symfony/config/Definition/NumericNode.phpÍa+X͉©<ç¶.vendor/symfony/config/Definition/Processor.php' a+X' Uú§¶8vendor/symfony/config/Definition/PrototypedArrayNode.php 'a+X 'ÝM/¨¶;vendor/symfony/config/Definition/PrototypeNodeInterface.phpwa+Xwÿ€/vendor/symfony/config/Definition/ScalarNode.php)a+X)iPi4¶1vendor/symfony/config/Definition/VariableNode.phpž a+Xž .[Íd¶Nvendor/symfony/config/Exception/FileLoaderImportCircularReferenceException.phpCa+XCWׯø¶;vendor/symfony/config/Exception/FileLoaderLoadException.phpç a+Xç £Ý×¶%vendor/symfony/config/FileLocator.phpj a+Xj :`wá¶.vendor/symfony/config/FileLocatorInterface.php{a+X{Këý¶1vendor/symfony/config/Loader/DelegatingLoader.phpba+Xb¤PÁm¶+vendor/symfony/config/Loader/FileLoader.php³ a+X³ 3øŸ$¶'vendor/symfony/config/Loader/Loader.phpFa+XFu|ïh¶0vendor/symfony/config/Loader/LoaderInterface.php¨a+X¨|;â¶/vendor/symfony/config/Loader/LoaderResolver.phpÂa+Xší$.¶8vendor/symfony/config/Loader/LoaderResolverInterface.phpa+X!ŠKĶ4vendor/symfony/config/Resource/DirectoryResource.phpî
a+Xî
>× ¶¶8vendor/symfony/config/Resource/FileExistenceResource.php†a+X†ûÓ0<C393>/vendor/symfony/config/Resource/FileResource.phpûa+Xû‡+_r¶4vendor/symfony/config/Resource/ResourceInterface.phpa+Xý?Љ¶>vendor/symfony/config/Resource/SelfCheckingResourceChecker.phpâa+Xâÿ¬@vendor/symfony/config/Resource/SelfCheckingResourceInterface.phpBa+XBÑ飶4vendor/symfony/config/ResourceCheckerConfigCache.phpÉa+XÉZÚWó¶;vendor/symfony/config/ResourceCheckerConfigCacheFactory.php(a+X(Œ2vendor/symfony/config/ResourceCheckerInterface.phpa+XœR'vendor/symfony/config/Util/XmlUtils.php¬a+X¬ ¶´&vendor/symfony/console/Application.phpa+X¶KðF¶*vendor/symfony/console/Command/Command.php0Ga+X0G8ÖÏ•¶.vendor/symfony/console/Command/HelpCommand.php« a+X« Ùqƒ¦¶.vendor/symfony/console/Command/ListCommand.php
a+X
ê (vendor/symfony/console/ConsoleEvents.php{a+X{ù6>¶<vendor/symfony/console/Descriptor/ApplicationDescription.phpa+X£“ï¶0vendor/symfony/console/Descriptor/Descriptor.php a+X õä¸ÿ¶9vendor/symfony/console/Descriptor/DescriptorInterface.phpáa+XáJZ0<¶4vendor/symfony/console/Descriptor/JsonDescriptor.php‰a+X‰t[ï˶8vendor/symfony/console/Descriptor/MarkdownDescriptor.php½a+X½Dy‰Ë¶4vendor/symfony/console/Descriptor/TextDescriptor.php'a+X'‚¶äò¶3vendor/symfony/console/Descriptor/XmlDescriptor.phpS%a+XS%¢àN<C3A0>4vendor/symfony/console/Event/ConsoleCommandEvent.php=a+X=%mf¶-vendor/symfony/console/Event/ConsoleEvent.php¸a+X¸¾õ
ž¶6vendor/symfony/console/Event/ConsoleExceptionEvent.php=a+X=·¦Ŷ6vendor/symfony/console/Event/ConsoleTerminateEvent.phpa+X{eð=vendor/symfony/console/Exception/CommandNotFoundException.php¾a+X¾Þøµì¶7vendor/symfony/console/Exception/ExceptionInterface.phpa+XãëÚU¶=vendor/symfony/console/Exception/InvalidArgumentException.php¾a+X¾îu i¶;vendor/symfony/console/Exception/InvalidOptionException.phpüa+Xüí“;3vendor/symfony/console/Exception/LogicException.phpªa+XªSML<4D>5vendor/symfony/console/Exception/RuntimeException.php®a+X®¶*b¶4vendor/symfony/console/Formatter/OutputFormatter.php?a+X?è½…¶=vendor/symfony/console/Formatter/OutputFormatterInterface.php]a+X]üy L¶9vendor/symfony/console/Formatter/OutputFormatterStyle.phpÀa+XÀD1©ð¶Bvendor/symfony/console/Formatter/OutputFormatterStyleInterface.php[a+X[f@†³¶>vendor/symfony/console/Formatter/OutputFormatterStyleStack.php& a+X& ]¶4»¶6vendor/symfony/console/Helper/DebugFormatterHelper.phpVa+XV˜Ý”ª¶2vendor/symfony/console/Helper/DescriptorHelper.php`
a+X`
+7*Ŷ1vendor/symfony/console/Helper/FormatterHelper.php a+X µí·ÿ¶(vendor/symfony/console/Helper/Helper.php a+X Œö­1vendor/symfony/console/Helper/HelperInterface.php¯a+X¯ïËèö¶+vendor/symfony/console/Helper/HelperSet.phpg
a+Xg
fÖô¶2vendor/symfony/console/Helper/InputAwareHelper.phpëa+Xëì“ë˶/vendor/symfony/console/Helper/ProcessHelper.php‡a+X‡Œ®A-vendor/symfony/console/Helper/ProgressBar.php.Aa+X.AÐX3vendor/symfony/console/Helper/ProgressIndicator.php!a+X!æ+žÁ¶0vendor/symfony/console/Helper/QuestionHelper.php¶4a+X¶4ói϶7vendor/symfony/console/Helper/SymfonyQuestionHelper.phpºa+XºÎQ "¶'vendor/symfony/console/Helper/Table.phpyLa+XyL„¨Ë޶+vendor/symfony/console/Helper/TableCell.phpVa+XV%0vendor/symfony/console/Helper/TableSeparator.phpEa+XEp|Õ¶,vendor/symfony/console/Helper/TableStyle.phpµa+Xµk„•¶*vendor/symfony/console/Input/ArgvInput.phpR(a+XR(œ‰ñ¶+vendor/symfony/console/Input/ArrayInput.php?a+X?u93z¶&vendor/symfony/console/Input/Input.phpþa+Xþ<00>î0¨¶.vendor/symfony/console/Input/InputArgument.php a+X æmB¶4vendor/symfony/console/Input/InputAwareInterface.php^a+X^9Kèh¶0vendor/symfony/console/Input/InputDefinition.php2-a+X2-Y»,¶/vendor/symfony/console/Input/InputInterface.phpna+XnlŠŠz¶,vendor/symfony/console/Input/InputOption.phpwa+Xw£8s¶,vendor/symfony/console/Input/StringInput.phpr a+Xr ýfIy¶/vendor/symfony/console/Logger/ConsoleLogger.phpêa+Xêáý´œ¶0vendor/symfony/console/Output/BufferedOutput.phpha+Xht|X4¶/vendor/symfony/console/Output/ConsoleOutput.phpîa+XÚ\¶8vendor/symfony/console/Output/ConsoleOutputInterface.phpKa+XKìÅ0ž¶,vendor/symfony/console/Output/NullOutput.phpna+Xnt<>D»¶(vendor/symfony/console/Output/Output.phpa+X„—Ÿ¶1vendor/symfony/console/Output/OutputInterface.phpr a+Xr éË>Z¶.vendor/symfony/console/Output/StreamOutput.php a+X ¸W®ê¶2vendor/symfony/console/Question/ChoiceQuestion.phpÂa+XÂn8vendor/symfony/console/Question/ConfirmationQuestion.php7a+X7âœ&ƒ¶,vendor/symfony/console/Question/Question.phpa+XnJ4vendor/symfony/console/Resources/bin/hiddeninput.exe$a+X$<>¥v¶,vendor/symfony/console/Style/OutputStyle.php$ a+X$ é/vendor/symfony/console/Style/StyleInterface.phpµ a+Xµ 3¨õZ¶-vendor/symfony/console/Style/SymfonyStyle.php>/a+X>/¸ (vendor/symfony/debug/BufferingLogger.phpða+XðM0 =¶vendor/symfony/debug/Debug.phpýa+Xýö<>)vendor/symfony/debug/DebugClassLoader.phpœ.a+Xœ.X“¶%vendor/symfony/debug/ErrorHandler.php¡`a+X¡`·òvN¶9vendor/symfony/debug/Exception/ClassNotFoundException.php<a+X<'D»<44>8vendor/symfony/debug/Exception/ContextErrorException.phpka+XkG 6vendor/symfony/debug/Exception/FatalErrorException.php a+X U¹®l¶6vendor/symfony/debug/Exception/FatalThrowableError.php4a+X4Ãì¶3vendor/symfony/debug/Exception/FlattenException.php¶a+X¶7vendor/symfony/debug/Exception/OutOfMemoryException.php¨a+X¨ªø =vendor/symfony/debug/Exception/UndefinedFunctionException.php+a+X+ÛOù¶;vendor/symfony/debug/Exception/UndefinedMethodException.php&a+X&ïyß¶)vendor/symfony/debug/ExceptionHandler.php[?a+X[?ù³@¶Ivendor/symfony/debug/FatalErrorHandler/ClassNotFoundFatalErrorHandler.phpÊa+XÊtãÐEvendor/symfony/debug/FatalErrorHandler/FatalErrorHandlerInterface.php¿a+X¿ÇiA¼¶Mvendor/symfony/debug/FatalErrorHandler/UndefinedFunctionFatalErrorHandler.phpõ a+Xõ Ìgý<67>Kvendor/symfony/debug/FatalErrorHandler/UndefinedMethodFatalErrorHandler.php>a+X>òï¡Ë¶Avendor/symfony/event-dispatcher/ContainerAwareEventDispatcher.phpŒa+XŒ¶¥ýͶBvendor/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php´,a+X´,ÿÅ<C3BF>Kvendor/symfony/event-dispatcher/Debug/TraceableEventDispatcherInterface.php#a+X#«Ó9vendor/symfony/event-dispatcher/Debug/WrappedListener.phpÕa+XÕðM} Mvendor/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.phpXa+XX¢¶6m¶)vendor/symfony/event-dispatcher/Event.phpaa+XaLÈ53vendor/symfony/event-dispatcher/EventDispatcher.phpºa+Xºø <vendor/symfony/event-dispatcher/EventDispatcherInterface.php a+X àȯŸ¶<vendor/symfony/event-dispatcher/EventSubscriberInterface.phpa+XÔÀ±¶0vendor/symfony/event-dispatcher/GenericEvent.php%a+X%Znú<vendor/symfony/event-dispatcher/ImmutableEventDispatcher.php~ a+X~ Ð(š¶:vendor/symfony/filesystem/Exception/ExceptionInterface.php½a+X½ =vendor/symfony/filesystem/Exception/FileNotFoundException.php·a+X·þ0z¶3vendor/symfony/filesystem/Exception/IOException.phpªa+XªÑ„A¶<vendor/symfony/filesystem/Exception/IOExceptionInterface.php¡a+X¡k/’ï¶(vendor/symfony/filesystem/Filesystem.php$\a+X$\ÝVD)vendor/symfony/filesystem/LockHandler.php÷ a+X÷ ç§¼¼¶/vendor/symfony/finder/Comparator/Comparator.phpa+Xç<>¶¶3vendor/symfony/finder/Comparator/DateComparator.php¹a+X¹ ¸'e¶5vendor/symfony/finder/Comparator/NumberComparator.php
a+X
l¯dJ¶9vendor/symfony/finder/Exception/AccessDeniedException.php«a+X«ÊcWÞ¶6vendor/symfony/finder/Exception/ExceptionInterface.phpïa+Xï7 vendor/symfony/finder/Finder.php¼Na+X¼N"æÃä¶vendor/symfony/finder/Glob.phpÙ a+XÙ þRœ`¶7vendor/symfony/finder/Iterator/CustomFilterIterator.phpa+Xúg3,¶:vendor/symfony/finder/Iterator/DateRangeFilterIterator.phpÅa+Xŧ'ýQ¶;vendor/symfony/finder/Iterator/DepthRangeFilterIterator.phpåa+Xå-,ìAvendor/symfony/finder/Iterator/ExcludeDirectoryFilterIterator.phpÀ a+XÀ ˶<vendor/symfony/finder/Iterator/FilecontentFilterIterator.php¦a+X¦rÜ~¶9vendor/symfony/finder/Iterator/FilenameFilterIterator.php•a+X• ËpÀ¶9vendor/symfony/finder/Iterator/FileTypeFilterIterator.phpYa+XY™]°%¶1vendor/symfony/finder/Iterator/FilterIterator.php“a+X“ÄAÇ;¶=vendor/symfony/finder/Iterator/MultiplePcreFilterIterator.php¸ a+X¸ ïq÷ж5vendor/symfony/finder/Iterator/PathFilterIterator.phpªa+Xªôù´„¶=vendor/symfony/finder/Iterator/RecursiveDirectoryIterator.php÷a+X÷l<06>:vendor/symfony/finder/Iterator/SizeRangeFilterIterator.php¢a+X¢¤§°¶3vendor/symfony/finder/Iterator/SortableIterator.phpã a+Xã ª¶*¶%vendor/symfony/finder/SplFileInfo.phpua+Xu"Pêü¶.vendor/symfony/polyfill-mbstring/bootstrap.php'a+X'p#Di¶-vendor/symfony/polyfill-mbstring/Mbstring.phpãSa+XãSº@vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.phpºIa+XºIê<>Òˆ@vendor/symfony/polyfill-mbstring/Resources/unidata/upperCase.php9Ja+X9J|Qù¶7vendor/symfony/process/Exception/ExceptionInterface.php¯a+X¯<1A><ž¶=vendor/symfony/process/Exception/InvalidArgumentException.phpða+XðË…<C38B>ë¶3vendor/symfony/process/Exception/LogicException.phpÒa+XÒ°¨W¶;vendor/symfony/process/Exception/ProcessFailedException.php<68>a+X<>´=vendor/symfony/process/Exception/ProcessTimedOutException.php{a+X{ÐÝ2»¶5vendor/symfony/process/Exception/RuntimeException.phpáa+Xá>H™¶+vendor/symfony/process/ExecutableFinder.php
a+X
1?å×¶&vendor/symfony/process/InputStream.phpœa+Xœ."„¶.vendor/symfony/process/PhpExecutableFinder.phpÎa+XΪ‡ï¶%vendor/symfony/process/PhpProcess.php† a+X† ÞöÉø¶.vendor/symfony/process/Pipes/AbstractPipes.phpa+Xœ×}Ô¶/vendor/symfony/process/Pipes/PipesInterface.phpøa+Xøã+’³¶*vendor/symfony/process/Pipes/UnixPipes.phpLa+XL*ú<à¶-vendor/symfony/process/Pipes/WindowsPipes.php a+X Ô8¶+¶"vendor/symfony/process/Process.phpкa+Xк¤ØÌœ¶)vendor/symfony/process/ProcessBuilder.phpœa+Xœž 2¶'vendor/symfony/process/ProcessUtils.php.a+X.0OßÞ¶&vendor/webmozart/assert/src/Assert.phpGza+XGzê&r¶robo<62>a+X<>/¤kÀ¶<?php
namespace Robo;
use Symfony\Component\Console\Application as SymfonyApplication;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
class Application extends SymfonyApplication
{
/**
* @param string $name
* @param string $version
*/
public function __construct($name, $version)
{
parent::__construct($name, $version);
$this->getDefinition()
->addOption(
new InputOption('--simulate', null, InputOption::VALUE_NONE, 'Run in simulated mode (show what would have happened).')
);
$this->getDefinition()
->addOption(
new InputOption('--progress-delay', null, InputOption::VALUE_REQUIRED, 'Number of seconds before progress bar is displayed in long-running task collections. Default: 2s.', Config::DEFAULT_PROGRESS_DELAY)
);
}
/**
* @param string $roboFile
* @param string $roboClass
*/
public function addInitRoboFileCommand($roboFile, $roboClass)
{
$createRoboFile = new Command('init');
$createRoboFile->setDescription("Intitalizes basic RoboFile in current dir");
$createRoboFile->setCode(function () use ($roboClass, $roboFile) {
$output = Robo::output();
$output->writeln("<comment> ~~~ Welcome to Robo! ~~~~ </comment>");
$output->writeln("<comment> ". basename($roboFile) ." will be created in the current directory </comment>");
file_put_contents(
$roboFile,
'<?php'
. "\n/**"
. "\n * This is project's console commands configuration for Robo task runner."
. "\n *"
. "\n * @see http://robo.li/"
. "\n */"
. "\nclass " . $roboClass . " extends \\Robo\\Tasks\n{\n // define public methods as commands\n}"
);
$output->writeln("<comment> Edit this file to add your commands! </comment>");
});
$this->add($createRoboFile);
}
}
<?php
namespace Robo\Collection;
use Robo\Result;
use Robo\Contract\TaskInterface;
use Robo\Collection\Collection;
/**
* Creates a task wrapper that converts any Callable into an
* object that can be used directly with a task collection.
*
* It is not necessary to use this class directly; Collection will
* automatically wrap Callables when they are added.
*/
class CallableTask implements TaskInterface
{
/**
* @var callable
*/
protected $fn;
/**
* @var \Robo\Contract\TaskInterface
*/
protected $reference;
public function __construct(callable $fn, TaskInterface $reference)
{
$this->fn = $fn;
$this->reference = $reference;
}
/**
* @return \Robo\Result
*/
public function run()
{
$result = call_user_func($this->fn);
// If the function returns no result, then count it
// as a success.
if (!isset($result)) {
$result = Result::success($this->reference);
}
// If the function returns a result, it must either return
// a \Robo\Result or an exit code. In the later case, we
// convert it to a \Robo\Result.
if (!$result instanceof Result) {
$result = new Result($this->reference, $result);
}
return $result;
}
}
<?php
namespace Robo\Collection;
use Robo\Result;
use Psr\Log\LogLevel;
use Robo\Contract\TaskInterface;
use Robo\Task\StackBasedTask;
use Robo\Task\BaseTask;
use Robo\TaskInfo;
use Robo\Contract\WrappedTaskInterface;
use Robo\Exception\TaskException;
use Robo\Exception\TaskExitException;
use Robo\Contract\CommandInterface;
use Robo\Common\ProgressIndicatorAwareTrait;
use Robo\Contract\InflectionInterface;
/**
* Group tasks into a collection that run together. Supports
* rollback operations for handling error conditions.
*
* This is an internal class. Clients should use a CollectionBuilder
* rather than direct use of the Collection class. @see CollectionBuilder.
*
* Below, the example FilesystemStack task is added to a collection,
* and associated with a rollback task. If any of the operations in
* the FilesystemStack, or if any of the other tasks also added to
* the task collection should fail, then the rollback function is
* called. Here, taskDeleteDir is used to remove partial results
* of an unfinished task.
*/
class Collection extends BaseTask implements CollectionInterface, CommandInterface
{
/**
* @var \Robo\Collection\Element[]
*/
protected $taskList = [];
/**
* @var TaskInterface[]
*/
protected $rollbackStack = [];
/**
* @var TaskInterface[]
*/
protected $completionStack = [];
/**
* @var CollectionInterface
*/
protected $parentCollection;
/**
* Constructor.
*/
public function __construct()
{
}
public function setProgressBarAutoDisplayInterval($interval)
{
if (!$this->progressIndicator) {
return;
}
return $this->progressIndicator->setProgressBarAutoDisplayInterval($interval);
}
/**
* {@inheritdoc}
*/
public function add(TaskInterface $task, $name = self::UNNAMEDTASK)
{
$task = new CompletionWrapper($this, $task);
$this->addToTaskList($name, $task);
return $this;
}
/**
* {@inheritdoc}
*/
public function addCode(callable $code, $name = self::UNNAMEDTASK)
{
return $this->add(new CallableTask($code, $this), $name);
}
/**
* {@inheritdoc}
*/
public function addIterable($iterable, callable $code)
{
$callbackTask = (new IterationTask($iterable, $code, $this))->inflect($this);
return $this->add($callbackTask);
}
/**
* {@inheritdoc}
*/
public function rollback(TaskInterface $rollbackTask)
{
// Rollback tasks always try as hard as they can, and never report failures.
$rollbackTask = $this->ignoreErrorsTaskWrapper($rollbackTask);
return $this->wrapAndRegisterRollback($rollbackTask);
}
/**
* {@inheritdoc}
*/
public function rollbackCode(callable $rollbackCode)
{
// Rollback tasks always try as hard as they can, and never report failures.
$rollbackTask = $this->ignoreErrorsCodeWrapper($rollbackCode);
return $this->wrapAndRegisterRollback($rollbackTask);
}
/**
* {@inheritdoc}
*/
public function completion(TaskInterface $completionTask)
{
$collection = $this;
$completionRegistrationTask = new CallableTask(
function () use ($collection, $completionTask) {
$collection->registerCompletion($completionTask);
},
$this
);
$this->addToTaskList(self::UNNAMEDTASK, $completionRegistrationTask);
return $this;
}
/**
* {@inheritdoc}
*/
public function completionCode(callable $completionTask)
{
$completionTask = new CallableTask($completionTask, $this);
return $this->completion($completionTask);
}
/**
* {@inheritdoc}
*/
public function before($name, $task, $nameOfTaskToAdd = self::UNNAMEDTASK)
{
return $this->addBeforeOrAfter(__FUNCTION__, $name, $task, $nameOfTaskToAdd);
}
/**
* {@inheritdoc}
*/
public function after($name, $task, $nameOfTaskToAdd = self::UNNAMEDTASK)
{
return $this->addBeforeOrAfter(__FUNCTION__, $name, $task, $nameOfTaskToAdd);
}
/**
* {@inheritdoc}
*/
public function progressMessage($text, $context = [], $level = LogLevel::NOTICE)
{
$context += ['name' => 'Progress'];
$context += TaskInfo::getTaskContext($this);
return $this->addCode(
function () use ($level, $text, $context) {
$this->printTaskOutput($level, $text, $context);
}
);
}
/**
* @param \Robo\Contract\TaskInterface $rollbackTask
*
* @return $this
*/
protected function wrapAndRegisterRollback(TaskInterface $rollbackTask)
{
$collection = $this;
$rollbackRegistrationTask = new CallableTask(
function () use ($collection, $rollbackTask) {
$collection->registerRollback($rollbackTask);
},
$this
);
$this->addToTaskList(self::UNNAMEDTASK, $rollbackRegistrationTask);
return $this;
}
/**
* Add either a 'before' or 'after' function or task.
*
* @param string $method
* @param string $name
* @param callable|TaskInterface $task
* @param string $nameOfTaskToAdd
*
* @return $this
*/
protected function addBeforeOrAfter($method, $name, $task, $nameOfTaskToAdd)
{
if (is_callable($task)) {
$task = new CallableTask($task, $this);
}
$existingTask = $this->namedTask($name);
$fn = [$existingTask, $method];
call_user_func($fn, $task, $nameOfTaskToAdd);
return $this;
}
/**
* Wrap the provided task in a wrapper that will ignore
* any errors or exceptions that may be produced. This
* is useful, for example, in adding optional cleanup tasks
* at the beginning of a task collection, to remove previous
* results which may or may not exist.
*
* TODO: Provide some way to specify which sort of errors
* are ignored, so that 'file not found' may be ignored,
* but 'permission denied' reported?
*
* @param \Robo\Contract\TaskInterface $task
*
* @return \Robo\Collection\CallableTask
*/
public function ignoreErrorsTaskWrapper(TaskInterface $task)
{
// If the task is a stack-based task, then tell it
// to try to run all of its operations, even if some
// of them fail.
if ($task instanceof StackBasedTask) {
$task->stopOnFail(false);
}
$ignoreErrorsInTask = function () use ($task) {
$data = [];
try {
$result = $this->runSubtask($task);
$message = $result->getMessage();
$data = $result->getData();
$data['exitcode'] = $result->getExitCode();
} catch (\Exception $e) {
$message = $e->getMessage();
}
return Result::success($task, $message, $data);
};
// Wrap our ignore errors callable in a task.
return new CallableTask($ignoreErrorsInTask, $this);
}
/**
* @param callable $task
*
* @return \Robo\Collection\CallableTask
*/
public function ignoreErrorsCodeWrapper(callable $task)
{
return $this->ignoreErrorsTaskWrapper(new CallableTask($task, $this));
}
/**
* Return the list of task names added to this collection.
*
* @return array
*/
public function taskNames()
{
return array_keys($this->taskList);
}
/**
* Test to see if a specified task name exists.
* n.b. before() and after() require that the named
* task exist; use this function to test first, if
* unsure.
*
* @param string $name
*
* @return bool
*/
public function hasTask($name)
{
return array_key_exists($name, $this->taskList);
}
/**
* Find an existing named task.
*
* @param string $name
* The name of the task to insert before. The named task MUST exist.
*
* @return Element
* The task group for the named task. Generally this is only
* used to call 'before()' and 'after()'.
*/
protected function namedTask($name)
{
if (!$this->hasTask($name)) {
throw new \RuntimeException("Could not find task named $name");
}
return $this->taskList[$name];
}
/**
* Add a list of tasks to our task collection.
*
* @param TaskInterface[] $tasks
* An array of tasks to run with rollback protection
*
* @return $this
*/
public function addTaskList(array $tasks)
{
foreach ($tasks as $name => $task) {
$this->add($task, $name);
}
return $this;
}
/**
* Add the provided task to our task list.
*
* @param string $name
* @param \Robo\Contract\TaskInterface $task
*
* @return \Robo\Collection\Collection
*/
protected function addToTaskList($name, TaskInterface $task)
{
// All tasks are stored in a task group so that we have a place
// to hang 'before' and 'after' tasks.
$taskGroup = new Element($task);
return $this->addCollectionElementToTaskList($name, $taskGroup);
}
/**
* @param int|string $name
* @param \Robo\Collection\Element $taskGroup
*
* @return $this
*/
protected function addCollectionElementToTaskList($name, Element $taskGroup)
{
// If a task name is not provided, then we'll let php pick
// the array index.
if (Result::isUnnamed($name)) {
$this->taskList[] = $taskGroup;
return $this;
}
// If we are replacing an existing task with the
// same name, ensure that our new task is added to
// the end.
$this->taskList[$name] = $taskGroup;
return $this;
}
/**
* Set the parent collection. This is necessary so that nested
* collections' rollback and completion tasks can be added to the
* top-level collection, ensuring that the rollbacks for a collection
* will run if any later task fails.
*
* @param \Robo\Collection\NestedCollectionInterface $parentCollection
*
* @return $this
*/
public function setParentCollection(NestedCollectionInterface $parentCollection)
{
$this->parentCollection = $parentCollection;
return $this;
}
/**
* Get the appropriate parent collection to use
*
* @return CollectionInterface
*/
public function getParentCollection()
{
return $this->parentCollection ? $this->parentCollection : $this;
}
/**
* Register a rollback task to run if there is any failure.
*
* Clients are free to add tasks to the rollback stack as
* desired; however, usually it is preferable to call
* Collection::rollback() instead. With that function,
* the rollback function will only be called if all of the
* tasks added before it complete successfully, AND some later
* task fails.
*
* One example of a good use-case for registering a callback
* function directly is to add a task that sends notification
* when a task fails.
*
* @param TaskInterface $rollbackTask
* The rollback task to run on failure.
*/
public function registerRollback(TaskInterface $rollbackTask)
{
if ($this->parentCollection) {
return $this->parentCollection->registerRollback($rollbackTask);
}
if ($rollbackTask) {
$this->rollbackStack[] = $rollbackTask;
}
}
/**
* Register a completion task to run once all other tasks finish.
* Completion tasks run whether or not a rollback operation was
* triggered. They do not trigger rollbacks if they fail.
*
* The typical use-case for a completion function is to clean up
* temporary objects (e.g. temporary folders). The preferred
* way to do that, though, is to use Temporary::wrap().
*
* On failures, completion tasks will run after all rollback tasks.
* If one task collection is nested inside another task collection,
* then the nested collection's completion tasks will run as soon as
* the nested task completes; they are not deferred to the end of
* the containing collection's execution.
*
* @param TaskInterface $completionTask
* The completion task to run at the end of all other operations.
*/
public function registerCompletion(TaskInterface $completionTask)
{
if ($this->parentCollection) {
return $this->parentCollection->registerCompletion($completionTask);
}
if ($completionTask) {
// Completion tasks always try as hard as they can, and never report failures.
$completionTask = $this->ignoreErrorsTaskWrapper($completionTask);
$this->completionStack[] = $completionTask;
}
}
/**
* Return the count of steps in this collection
*
* @return int
*/
public function progressIndicatorSteps()
{
$steps = 0;
foreach ($this->taskList as $name => $taskGroup) {
$steps += $taskGroup->progressIndicatorSteps();
}
return $steps;
}
/**
* A Collection of tasks can provide a command via `getCommand()`
* if it contains a single task, and that task implements CommandInterface.
*
* @return string
*
* @throws \Robo\Exception\TaskException
*/
public function getCommand()
{
if (empty($this->taskList)) {
return '';
}
if (count($this->taskList) > 1) {
// TODO: We could potentially iterate over the items in the collection
// and concatenate the result of getCommand() from each one, and fail
// only if we encounter a command that is not a CommandInterface.
throw new TaskException($this, "getCommand() does not work on arbitrary collections of tasks.");
}
$taskElement = reset($this->taskList);
$task = $taskElement->getTask();
$task = ($task instanceof WrappedTaskInterface) ? $task->original() : $task;
if ($task instanceof CommandInterface) {
return $task->getCommand();
}
throw new TaskException($task, get_class($task) . " does not implement CommandInterface, so can't be used to provide a command");
}
/**
* Run our tasks, and roll back if necessary.
*
* @return \Robo\Result
*/
public function run()
{
$result = $this->runWithoutCompletion();
$this->complete();
return $result;
}
/**
* @return \Robo\Result
*/
private function runWithoutCompletion()
{
$result = Result::success($this);
if (empty($this->taskList)) {
return $result;
}
$this->startProgressIndicator();
if ($result->wasSuccessful()) {
foreach ($this->taskList as $name => $taskGroup) {
$taskList = $taskGroup->getTaskList();
$result = $this->runTaskList($name, $taskList, $result);
if (!$result->wasSuccessful()) {
$this->fail();
return $result;
}
}
$this->taskList = [];
}
$this->stopProgressIndicator();
$result['time'] = $this->getExecutionTime();
return $result;
}
/**
* Run every task in a list, but only up to the first failure.
* Return the failing result, or success if all tasks run.
*
* @param string $name
* @param TaskInterface[] $taskList
* @param \Robo\Result $result
*
* @return \Robo\Result
*
* @throws \Robo\Exception\TaskExitException
*/
private function runTaskList($name, array $taskList, Result $result)
{
try {
foreach ($taskList as $taskName => $task) {
$taskResult = $this->runSubtask($task);
$this->advanceProgressIndicator();
// If the current task returns an error code, then stop
// execution and signal a rollback.
if (!$taskResult->wasSuccessful()) {
return $taskResult;
}
// We accumulate our results into a field so that tasks that
// have a reference to the collection may examine and modify
// the incremental results, if they wish.
$key = Result::isUnnamed($taskName) ? $name : $taskName;
$result->accumulate($key, $taskResult);
}
} catch (TaskExitException $exitException) {
$this->fail();
throw $exitException;
} catch (\Exception $e) {
// Tasks typically should not throw, but if one does, we will
// convert it into an error and roll back.
return Result::fromException($task, $e, $result->getData());
}
return $result;
}
/**
* Force the rollback functions to run
*
* @return $this
*/
public function fail()
{
$this->disableProgressIndicator();
$this->runRollbackTasks();
$this->complete();
return $this;
}
/**
* Force the completion functions to run
*
* @return $this
*/
public function complete()
{
$this->detatchProgressIndicator();
$this->runTaskListIgnoringFailures($this->completionStack);
$this->reset();
return $this;
}
/**
* Reset this collection, removing all tasks.
*
* @return $this
*/
public function reset()
{
$this->taskList = [];
$this->completionStack = [];
$this->rollbackStack = [];
return $this;
}
/**
* Run all of our rollback tasks.
*
* Note that Collection does not implement RollbackInterface, but
* it may still be used as a task inside another task collection
* (i.e. you can nest task collections, if desired).
*/
protected function runRollbackTasks()
{
$this->runTaskListIgnoringFailures($this->rollbackStack);
// Erase our rollback stack once we have finished rolling
// everything back. This will allow us to potentially use
// a command collection more than once (e.g. to retry a
// failed operation after doing some error recovery).
$this->rollbackStack = [];
}
/**
* @param TaskInterface|NestedCollectionInterface|WrappedTaskInterface $task
*
* @return \Robo\Result
*/
protected function runSubtask($task)
{
$original = ($task instanceof WrappedTaskInterface) ? $task->original() : $task;
$this->setParentCollectionForTask($original, $this->getParentCollection());
if ($original instanceof InflectionInterface) {
$original->inflect($this);
}
$taskResult = $task->run();
return $taskResult;
}
/**
* @param TaskInterface|NestedCollectionInterface|WrappedTaskInterface $task
* @param $parentCollection
*/
protected function setParentCollectionForTask($task, $parentCollection)
{
if ($task instanceof NestedCollectionInterface) {
$task->setParentCollection($parentCollection);
}
}
/**
* Run all of the tasks in a provided list, ignoring failures.
* This is used to roll back or complete.
*
* @param TaskInterface[] $taskList
*/
protected function runTaskListIgnoringFailures(array $taskList)
{
foreach ($taskList as $task) {
try {
$this->runSubtask($task);
} catch (\Exception $e) {
// Ignore rollback failures.
}
}
}
/**
* Give all of our tasks to the provided collection builder.
*
* @param CollectionBuilder $builder
*/
public function transferTasks($builder)
{
foreach ($this->taskList as $name => $taskGroup) {
// TODO: We are abandoning all of our before and after tasks here.
// At the moment, transferTasks is only called under conditions where
// there will be none of these, but care should be taken if that changes.
$task = $taskGroup->getTask();
$builder->addTaskToCollection($task);
}
$this->reset();
}
}
<?php
namespace Robo\Collection;
use Guzzle\Inflection\InflectorInterface;
use Robo\Config;
use Robo\Common\Timer;
use Psr\Log\LogLevel;
use Robo\Contract\InflectionInterface;
use Robo\Contract\TaskInterface;
use Robo\Contract\CompletionInterface;
use Robo\Contract\WrappedTaskInterface;
use Robo\Collection\NestedCollectionInterface;
use Robo\LoadAllTasks;
use Robo\Task\Simulator;
use Robo\Collection\CompletionWrapper;
use Robo\Collection\Temporary;
use Robo\Contract\ConfigAwareInterface;
use Robo\Common\ConfigAwareTrait;
use ReflectionClass;
use Robo\Task\BaseTask;
use Robo\Contract\BuilderAwareInterface;
use Robo\Contract\CommandInterface;
use Robo\Exception\TaskException;
/**
* Creates a collection, and adds tasks to it. The collection builder
* offers a streamlined chained-initialization mechanism for easily
* creating task groups. Facilities for creating working and temporary
* directories are also provided.
*
* ``` php
* <?php
* $result = $this->collectionBuilder()
* ->taskFilesystemStack()
* ->mkdir('g')
* ->touch('g/g.txt')
* ->rollback(
* $this->taskDeleteDir('g')
* )
* ->taskFilesystemStack()
* ->mkdir('g/h')
* ->touch('g/h/h.txt')
* ->taskFilesystemStack()
* ->mkdir('g/h/i/c')
* ->touch('g/h/i/i.txt')
* ->run()
* ?>
*
* In the example above, the `taskDeleteDir` will be called if
* ```
*/
class CollectionBuilder extends BaseTask implements NestedCollectionInterface, WrappedTaskInterface, CommandInterface
{
/**
* @var \Robo\Tasks
*/
protected $commandFile;
/**
* @var CollectionInterface
*/
protected $collection;
/**
* @var TaskInterface
*/
protected $currentTask;
/**
* @var bool
*/
protected $simulated;
/**
* @param \Robo\Tasks $commandFile
*/
public function __construct($commandFile)
{
$this->commandFile = $commandFile;
}
/**
* @param bool $simulated
*
* @return $this
*/
public function simulated($simulated = true)
{
$this->simulated = $simulated;
return $this;
}
/**
* @return bool
*/
public function isSimulated()
{
if (!isset($this->simulated)) {
$this->simulated = $this->getConfig()->get(Config::SIMULATE);
}
return $this->simulated;
}
/**
* Create a temporary directory to work in. When the collection
* completes or rolls back, the temporary directory will be deleted.
* Returns the path to the location where the directory will be
* created.
*
* @param string $prefix
* @param string $base
* @param bool $includeRandomPart
*
* @return string
*/
public function tmpDir($prefix = 'tmp', $base = '', $includeRandomPart = true)
{
// n.b. Any task that the builder is asked to create is
// automatically added to the builder's collection, and
// wrapped in the builder object. Therefore, the result
// of any call to `taskFoo()` from within the builder will
// always be `$this`.
return $this->taskTmpDir($prefix, $base, $includeRandomPart)->getPath();
}
/**
* Create a working directory to hold results. A temporary directory
* is first created to hold the intermediate results. After the
* builder finishes, the work directory is moved into its final location;
* any results already in place will be moved out of the way and
* then deleted.
*
* @param string $finalDestination The path where the working directory
* will be moved once the task collection completes.
*
* @return string
*/
public function workDir($finalDestination)
{
// Creating the work dir task in this context adds it to our task collection.
return $this->taskWorkDir($finalDestination)->getPath();
}
public function addTask(TaskInterface $task)
{
$this->getCollection()->add($task);
return $this;
}
public function addCode(callable $code)
{
$this->getCollection()->addCode($code);
return $this;
}
/**
* Add a list of tasks to our task collection.
*
* @param TaskInterface[] $tasks
* An array of tasks to run with rollback protection
*
* @return $this
*/
public function addTaskList(array $tasks)
{
$this->getCollection()->addTaskList($tasks);
return $this;
}
public function rollback(TaskInterface $task)
{
// Ensure that we have a collection if we are going to add
// a rollback function.
$this->getCollection()->rollback($task);
return $this;
}
public function rollbackCode(callable $rollbackCode)
{
$this->getCollection()->rollbackCode($rollbackCode);
return $this;
}
public function completion(TaskInterface $task)
{
$this->getCollection()->completion($task);
return $this;
}
public function completionCode(callable $completionCode)
{
$this->getCollection()->completionCode($completionCode);
return $this;
}
/**
* @param string $text
* @param array $context
* @param string $level
*
* @return $this
*/
public function progressMessage($text, $context = [], $level = LogLevel::NOTICE)
{
$this->getCollection()->progressMessage($text, $context, $level);
return $this;
}
/**
* @param \Robo\Collection\NestedCollectionInterface $parentCollection
*
* @return $this
*/
public function setParentCollection(NestedCollectionInterface $parentCollection)
{
$this->getCollection()->setParentCollection($parentCollection);
return $this;
}
/**
* Called by the factory method of each task; adds the current
* task to the task builder.
*
* TODO: protected
*
* @param TaskInterface $task
*
* @return $this
*/
public function addTaskToCollection($task)
{
// Postpone creation of the collection until the second time
// we are called. At that time, $this->currentTask will already
// be populated. We call 'getCollection()' so that it will
// create the collection and add the current task to it.
// Note, however, that if our only tasks implements NestedCollectionInterface,
// then we should force this builder to use a collection.
if (!$this->collection && (isset($this->currentTask) || ($task instanceof NestedCollectionInterface))) {
$this->getCollection();
}
$this->currentTask = $task;
if ($this->collection) {
$this->collection->add($task);
}
return $this;
}
/**
* Return the current task for this collection builder.
* TODO: Not needed?
*
* @return \Robo\Contract\TaskInterface
*/
public function getCollectionBuilderCurrentTask()
{
return $this->currentTask;
}
/**
* Create a new builder with its own task collection
*
* @return CollectionBuilder
*/
public function newBuilder()
{
$collectionBuilder = new self($this->commandFile);
$collectionBuilder->inflect($this);
$collectionBuilder->simulated($this->isSimulated());
return $collectionBuilder;
}
/**
* Calling the task builder with methods of the current
* task calls through to that method of the task.
*
* There is extra complexity in this function that could be
* simplified if we attached the 'LoadAllTasks' and custom tasks
* to the collection builder instead of the RoboFile. While that
* change would be a better design overall, it would require that
* the user do a lot more work to set up and use custom tasks.
* We therefore take on some additional complexity here in order
* to allow users to maintain their tasks in their RoboFile, which
* is much more convenient.
*
* Calls to $this->collectionBuilder()->taskFoo() cannot be made
* directly because all of the task methods are protected. These
* calls will therefore end up here. If the method name begins
* with 'task', then it is eligible to be used with the builder.
*
* When we call getBuiltTask, below, it will use the builder attached
* to the commandfile to build the task. However, this is not what we
* want: the task needs to be built from THIS collection builder, so that
* it will be affected by whatever state is active in this builder.
* To do this, we have two choices: 1) save and restore the builder
* in the commandfile, or 2) clone the commandfile and set this builder
* on the copy. 1) is vulnerable to failure in multithreaded environments
* (currently not supported), while 2) might cause confusion if there
* is shared state maintained in the commandfile, which is in the
* domain of the user.
*
* Note that even though we are setting up the commandFile to
* use this builder, getBuiltTask always creates a new builder
* (which is constructed using all of the settings from the
* commandFile's builder), and the new task is added to that.
* We therefore need to transfer the newly built task into this
* builder. The temporary builder is discarded.
*
* @param string $fn
* @param array $args
*
* @return $this|mixed
*/
public function __call($fn, $args)
{
if (preg_match('#^task[A-Z]#', $fn) && (method_exists($this->commandFile, 'getBuiltTask'))) {
$saveBuilder = $this->commandFile->getBuilder();
$this->commandFile->setBuilder($this);
$temporaryBuilder = $this->commandFile->getBuiltTask($fn, $args);
$this->commandFile->setBuilder($saveBuilder);
if (!$temporaryBuilder) {
throw new \BadMethodCallException("No such method $fn: task does not exist in " . get_class($this->commandFile));
}
$temporaryBuilder->getCollection()->transferTasks($this);
return $this;
}
if (!isset($this->currentTask)) {
throw new \BadMethodCallException("No such method $fn: current task undefined in collection builder.");
}
// If the method called is a method of the current task,
// then call through to the current task's setter method.
$result = call_user_func_array([$this->currentTask, $fn], $args);
// If something other than a setter method is called, then return its result.
$currentTask = ($this->currentTask instanceof WrappedTaskInterface) ? $this->currentTask->original() : $this->currentTask;
if (isset($result) && ($result !== $currentTask)) {
return $result;
}
return $this;
}
/**
* Construct the desired task and add it to this builder.
*
* @param string|object $name
* @param array $args
*
* @return \Robo\Collection\CollectionBuilder
*/
public function build($name, $args)
{
$reflection = new ReflectionClass($name);
$task = $reflection->newInstanceArgs($args);
if (!$task) {
throw new RuntimeException("Can not construct task $name");
}
$task = $this->fixTask($task, $args);
return $this->addTaskToCollection($task);
}
/**
* @param InflectionInterface $task
* @param array $args
*
* @return \Robo\Collection\CompletionWrapper|\Robo\Task\Simulator
*/
protected function fixTask($task, $args)
{
$task->inflect($this);
if ($task instanceof BuilderAwareInterface) {
$task->setBuilder($this);
}
// Do not wrap our wrappers.
if ($task instanceof CompletionWrapper || $task instanceof Simulator) {
return $task;
}
// Remember whether or not this is a task before
// it gets wrapped in any decorator.
$isTask = $task instanceof TaskInterface;
$isCollection = $task instanceof NestedCollectionInterface;
// If the task implements CompletionInterface, ensure
// that its 'complete' method is called when the application
// terminates -- but only if its 'run' method is called
// first. If the task is added to a collection, then the
// task will be unwrapped via its `original` method, and
// it will be re-wrapped with a new completion wrapper for
// its new collection.
if ($task instanceof CompletionInterface) {
$task = new CompletionWrapper(Temporary::getCollection(), $task);
}
// If we are in simulated mode, then wrap any task in
// a TaskSimulator.
if ($isTask && !$isCollection && ($this->isSimulated())) {
$task = new \Robo\Task\Simulator($task, $args);
$task->inflect($this);
}
return $task;
}
/**
* When we run the collection builder, run everything in the collection.
*
* @return \Robo\Result
*/
public function run()
{
$this->startTimer();
$result = $this->runTasks();
$this->stopTimer();
$result['time'] = $this->getExecutionTime();
return $result;
}
/**
* If there is a single task, run it; if there is a collection, run
* all of its tasks.
*
* @return \Robo\Result
*/
protected function runTasks()
{
if (!$this->collection && $this->currentTask) {
return $this->currentTask->run();
}
return $this->getCollection()->run();
}
/**
* @return string
*/
public function getCommand()
{
if (!$this->collection && $this->currentTask) {
$task = $this->currentTask;
$task = ($task instanceof WrappedTaskInterface) ? $task->original() : $task;
if ($task instanceof CommandInterface) {
return $task->getCommand();
}
}
return $this->getCollection()->getCommand();
}
/**
* @return \Robo\Collection\Collection
*/
public function original()
{
return $this->getCollection();
}
/**
* Return the collection of tasks associated with this builder.
*
* @return CollectionInterface
*/
public function getCollection()
{
if (!isset($this->collection)) {
$this->collection = new Collection();
$this->collection->inflect($this);
$this->collection->setProgressBarAutoDisplayInterval($this->getConfig()->get(Config::PROGRESS_BAR_AUTO_DISPLAY_INTERVAL));
if (isset($this->currentTask)) {
$this->collection->add($this->currentTask);
}
}
return $this->collection;
}
}
<?php
namespace Robo\Collection;
use Psr\Log\LogLevel;
use Robo\Contract\TaskInterface;
interface CollectionInterface extends NestedCollectionInterface
{
/**
* Unnamed tasks are assigned an arbitrary numeric index
* in the task list. Any numeric value may be used, but the
* UNNAMEDTASK constant is recommended for clarity.
*
* @var int
*/
const UNNAMEDTASK = 0;
/**
* Add a task or a list of tasks to our task collection. Each task
* will run via its 'run()' method once (and if) all of the tasks
* added before it complete successfully. If the task also implements
* RollbackInterface, then it will be rolled back via its 'rollback()'
* method ONLY if its 'run()' method completes successfully, and some
* task added after it fails.
*
* @param TaskInterface $task
* The task to add to our collection.
* @param int|string $name
* An optional name for the task -- missing or UNNAMEDTASK for unnamed tasks.
* Names are used for positioning before and after tasks.
*
* @return CollectionInterface
*/
public function add(TaskInterface $task, $name = self::UNNAMEDTASK);
/**
* Add arbitrary code to execute as a task.
*
* @param callable $code Code to execute as a task
* @param int|string $name
* An optional name for the task -- missing or UNNAMEDTASK for unnamed tasks.
* Names are used for positioning before and after tasks.
*
* @return $this
*/
public function addCode(callable $code, $name = self::UNNAMEDTASK);
/**
* Add arbitrary code that will be called once for every item in the
* provided array or iterable object. If the function result of the
* provided callback is a TaskInterface or Collection, then it will be
* executed.
*
* @param CollectionInterface|array $iterable A collection of things to iterate
* @param $code $code A callback function to call for each item in the collection.
*
* @return $this
*/
public function addIterable($iterable, callable $code);
/**
* Add a rollback task to our task collection. A rollback task
* will execute ONLY if all of the tasks added before it complete
* successfully, AND some task added after it fails.
*
* @param TaskInterface $rollbackTask
* The rollback task to add. Note that the 'run()' method of the
* task executes, not its 'rollback()' method. To use the 'rollback()'
* method, add the task via 'Collection::add()' instead.
*
* @return $this
*/
public function rollback(TaskInterface $rollbackTask);
/**
* Add arbitrary code to execute as a rollback.
*
* @param callable $rollbackTask Code to execute during rollback processing
*
* @return $this
*/
public function rollbackCode(callable $rollbackTask);
/**
* Add a completion task to our task collection. A completion task
* will execute EITHER after all tasks succeed, OR immediatley after
* any task fails. Completion tasks never cause errors to be returned
* from Collection::run(), even if they fail.
*
* @param TaskInterface $completionTask
* The completion task to add. Note that the 'run()' method of the
* task executes, just as if the task was added normally.
*
* @return $this
*/
public function completion(TaskInterface $completionTask);
/**
* Add arbitrary code to execute as a completion.
*
* @param callable $completionTask Code to execute after collection completes
*
* @return $this
*/
public function completionCode(callable $completionTask);
/**
* Add a task before an existing named task.
*
* @param string $name
* The name of the task to insert before. The named task MUST exist.
* @param callable|TaskInterface $task
* The task to add.
* @param int|string $nameOfTaskToAdd
* The name of the task to add. If not provided, will be associated
* with the named task it was added before.
*
* @return $this
*/
public function before($name, $task, $nameOfTaskToAdd = self::UNNAMEDTASK);
/**
* Add a task after an existing named task.
*
* @param string $name
* The name of the task to insert before. The named task MUST exist.
* @param callable|TaskInterface $task
* The task to add.
* @param int|string $nameOfTaskToAdd
* The name of the task to add. If not provided, will be associated
* with the named task it was added after.
*
* @return $this
*/
public function after($name, $task, $nameOfTaskToAdd = self::UNNAMEDTASK);
/**
* Print a progress message after Collection::run() has executed
* all of the tasks that were added prior to the point when this
* method was called. If one of the previous tasks fail, then this
* message will not be printed.
*
* @param string $text Message to print.
* @param array $context Extra context data for use by the logger.
* @param \Psr\Log\LogLevel|string $level The log level to print the information at. Default is NOTICE.
*
* @return $this
*/
public function progressMessage($text, $context = [], $level = LogLevel::NOTICE);
}
<?php
namespace Robo\Collection;
use Consolidation\AnnotatedCommand\Hooks\ProcessResultInterface;
use Consolidation\AnnotatedCommand\CommandData;
use Robo\Contract\TaskInterface;
use Robo\Result;
/**
* The collection process hook is added to the annotation command
* hook manager in Runner::configureContainer(). This hook will be
* called every time a command runs. If the command result is a
* \Robo\Contract\TaskInterface (in particular, \Robo\Collection\Collection),
* then we run the collection, and return the result. We ignore results
* of any other type.
*/
class CollectionProcessHook implements ProcessResultInterface
{
/**
* @param \Robo\Result|\Robo\Contract\TaskInterface $result
* @param \Consolidation\AnnotatedCommand\CommandData $commandData
*
* @return null|\Robo\Result
*/
public function process($result, CommandData $commandData)
{
if ($result instanceof TaskInterface) {
try {
return $result->run();
} catch (\Exception $e) {
return Result::fromException($result, $e);
}
}
}
}
<?php
namespace Robo\Collection;
use Robo\Task\BaseTask;
use Robo\Contract\TaskInterface;
use Robo\Contract\RollbackInterface;
use Robo\Contract\CompletionInterface;
use Robo\Contract\WrappedTaskInterface;
/**
* Creates a task wrapper that will manage rollback and collection
* management to a task when it runs. Tasks are automatically
* wrapped in a CompletionWrapper when added to a task collection.
*
* Clients may need to wrap their task in a CompletionWrapper if it
* creates temporary objects.
*
* @see \Robo\Task\Filesystem\loadTasks::taskTmpDir
*/
class CompletionWrapper extends BaseTask implements WrappedTaskInterface
{
/**
* @var \Robo\Collection\Collection
*/
private $collection;
/**
* @var \Robo\Contract\TaskInterface
*/
private $task;
/**
* @var NULL|\Robo\Contract\TaskInterface
*/
private $rollbackTask;
/**
* Create a CompletionWrapper.
*
* Temporary tasks are always wrapped in a CompletionWrapper, as are
* any tasks that are added to a collection. If a temporary task
* is added to a collection, then it is first unwrapped from its
* CompletionWrapper (via its original() method), and then added to a
* new CompletionWrapper for the collection it is added to.
*
* In this way, when the CompletionWrapper is finally executed, the
* task's rollback and completion handlers will be registered on
* whichever collection it was registered on.
*
* @todo Why not CollectionInterface the type of the $collection argument?
*
* @param \Robo\Collection\Collection $collection
* @param \Robo\Contract\TaskInterface $task
* @param \Robo\Contract\TaskInterface|NULL $rollbackTask
*/
public function __construct(Collection $collection, TaskInterface $task, TaskInterface $rollbackTask = null)
{
$this->collection = $collection;
$this->task = ($task instanceof WrappedTaskInterface) ? $task->original() : $task;
$this->rollbackTask = $rollbackTask;
}
/**
* {@inheritdoc}
*/
public function original()
{
return $this->task;
}
/**
* Before running this task, register its rollback and completion
* handlers on its collection. The reason this class exists is to
* defer registration of rollback and completion tasks until 'run()' time.
*
* @return \Robo\Result
*/
public function run()
{
if ($this->rollbackTask) {
$this->collection->registerRollback($this->rollbackTask);
}
if ($this->task instanceof RollbackInterface) {
$this->collection->registerRollback(new CallableTask([$this->task, 'rollback'], $this->task));
}
if ($this->task instanceof CompletionInterface) {
$this->collection->registerCompletion(new CallableTask([$this->task, 'complete'], $this->task));
}
return $this->task->run();
}
/**
* Make this wrapper object act like the class it wraps.
*
* @param string $function
* @param array $args
*
* @return mixed
*/
public function __call($function, $args)
{
return call_user_func_array(array($this->task, $function), $args);
}
}
<?php
namespace Robo\Collection;
use Robo\Contract\TaskInterface;
use Robo\Contract\WrappedTaskInterface;
use Robo\Contract\ProgressIndicatorAwareInterface;
/**
* One element in a collection. Each element consists of a task
* all of its before tasks, and all of its after tasks.
*
* This class is internal to Collection; it should not be used directly.
*/
class Element
{
/**
* @var \Robo\Contract\TaskInterface
*/
protected $task;
/**
* @var array
*/
protected $before = [];
/**
* @var array
*/
protected $after = [];
public function __construct(TaskInterface $task)
{
$this->task = $task;
}
/**
* @param mixed $before
* @param string $name
*/
public function before($before, $name)
{
if ($name) {
$this->before[$name] = $before;
} else {
$this->before[] = $before;
}
}
/**
* @param mixed $after
* @param string $name
*/
public function after($after, $name)
{
if ($name) {
$this->after[$name] = $after;
} else {
$this->after[] = $after;
}
}
/**
* @return array
*/
public function getBefore()
{
return $this->before;
}
/**
* @return array
*/
public function getAfter()
{
return $this->after;
}
/**
* @return \Robo\Contract\TaskInterface
*/
public function getTask()
{
return $this->task;
}
/**
* @return array
*/
public function getTaskList()
{
return array_merge($this->getBefore(), [$this->getTask()], $this->getAfter());
}
/**
* @return int
*/
public function progressIndicatorSteps()
{
$steps = 0;
foreach ($this->getTaskList() as $task) {
if ($task instanceof WrappedTaskInterface) {
$task = $task->original();
}
// If the task is a ProgressIndicatorAwareInterface, then it
// will advance the progress indicator a number of times.
if ($task instanceof ProgressIndicatorAwareInterface) {
$steps += $task->progressIndicatorSteps();
}
// We also advance the progress indicator once regardless
// of whether it is progress-indicator aware or not.
$steps++;
}
return $steps;
}
}
<?php
namespace Robo\Collection;
trait loadTasks
{
/**
* Run a callback function on each item in a collection
*
* @param array $collection
*
* @return \Robo\Collection\TaskForEach
*/
protected function taskForEach($collection)
{
return $this->task(TaskForEach::class, $collection);
}
}
<?php
namespace Robo\Collection;
use Psr\Log\LogLevel;
use Robo\Contract\TaskInterface;
interface NestedCollectionInterface
{
/**
* @param \Robo\Collection\NestedCollectionInterface $parentCollection
*
* @return $this
*/
public function setParentCollection(NestedCollectionInterface $parentCollection);
}
<?php
namespace Robo\Collection;
use Robo\Collection\NestedCollectionInterface;
use Robo\Result;
use Robo\TaskInfo;
use Robo\Task\BaseTask;
use Robo\Contract\TaskInterface;
use Robo\Contract\BuilderAwareInterface;
use Robo\Common\BuilderAwareTrait;
/**
* Creates a task wrapper that converts any Callable into an
* object that will execute the callback once for each item in the
* provided collection.
*
* It is not necessary to use this class directly; Collection::addIterable
* will automatically create one when it is called.
*/
class TaskForEach extends BaseTask implements NestedCollectionInterface, BuilderAwareInterface
{
use BuilderAwareTrait;
/**
* @var callable[]
*/
protected $functionStack = [];
/**
* @var callable[]
*/
protected $countingStack = [];
/**
* @var string
*/
protected $message;
/**
* @var array
*/
protected $context = [];
protected $iterable;
/**
* @var \Robo\Collection\NestedCollectionInterface
*/
protected $parentCollection;
public function __construct($iterable)
{
$this->iterable = $iterable;
}
/**
* @param string $message
* @param array $context
*
* @return $this
*/
public function iterationMessage($message, $context = [])
{
$this->message = $message;
$this->context = $context + ['name' => 'Progress'];
return $this;
}
/**
* @param int|string $key
* @param mixed $value
*/
protected function showIterationMessage($key, $value)
{
if ($this->message) {
$context = ['key' => $key, 'value' => $value];
$context += $this->context;
$context += TaskInfo::getTaskContext($this);
$this->printTaskInfo($this->message, $context);
}
}
/**
* @param callable $fn
*
* @return $this
*/
public function withEachKeyValueCall(callable $fn)
{
$this->functionStack[] = $fn;
return $this;
}
/**
* @param callable $fn
*
* @return \Robo\Collection\TaskForEach
*/
public function call(callable $fn)
{
return $this->withEachKeyValueCall(
function ($key, $value) use ($fn) {
return call_user_func($fn, $value);
}
);
}
/**
* @param callable $fn
*
* @return \Robo\Collection\TaskForEach
*/
public function withBuilder(callable $fn)
{
$this->countingStack[] =
function ($key, $value) use ($fn) {
// Create a new builder for every iteration
$builder = $this->collectionBuilder();
// The user function should build task operations using
// the $key / $value parameters; we will call run() on
// the builder thus constructed.
call_user_func($fn, $builder, $key, $value);
return $builder->getCollection()->progressIndicatorSteps();
};
return $this->withEachKeyValueCall(
function ($key, $value) use ($fn) {
// Create a new builder for every iteration
$builder = $this->collectionBuilder()
->setParentCollection($this->parentCollection);
// The user function should build task operations using
// the $key / $value parameters; we will call run() on
// the builder thus constructed.
call_user_func($fn, $builder, $key, $value);
return $builder->run();
}
);
}
/**
* {@inheritdoc}
*/
public function setParentCollection(NestedCollectionInterface $parentCollection)
{
$this->parentCollection = $parentCollection;
return $this;
}
/**
* {@inheritdoc}
*/
public function progressIndicatorSteps()
{
$multiplier = count($this->functionStack);
if (!empty($this->countingStack)) {
$value = reset($this->iterable);
$key = key($this->iterable);
foreach ($this->countingStack as $fn) {
$multiplier += call_user_func($fn, $key, $value);
}
}
return count($this->iterable) * $multiplier;
}
/**
* {@inheritdoc}
*/
public function run()
{
$finalResult = Result::success($this);
$this->startProgressIndicator();
foreach ($this->iterable as $key => $value) {
$this->showIterationMessage($key, $value);
try {
foreach ($this->functionStack as $fn) {
$result = call_user_func($fn, $key, $value);
$this->advanceProgressIndicator();
if (!isset($result)) {
$result = Result::success($this);
}
// If the function returns a result, it must either return
// a \Robo\Result or an exit code. In the later case, we
// convert it to a \Robo\Result.
if (!$result instanceof Result) {
$result = new Result($this, $result);
}
if (!$result->wasSuccessful()) {
return $result;
}
$finalResult = $result->merge($finalResult);
}
} catch (\Exception $e) {
return Result::fromException($result, $e);
}
}
$this->stopProgressIndicator();
return $finalResult;
}
}
<?php
namespace Robo\Collection;
use Robo\Contract\TaskInterface;
/**
* The temporary collection keeps track of the global collection of
* temporary cleanup tasks in instances where temporary-generating
* tasks are executed directly via their run() method, rather than
* as part of a collection.
*
* In general, temporary-generating tasks should always be run in
* a collection, as the cleanup functions registered with the
* Temporary collection will not run until requested.
*
* Since the results could be undefined if cleanup functions were called
* at arbitrary times during a program's execution, cleanup should only
* be done immeidately prior to program termination, when there is no
* danger of cleaning up after some unrelated task.
*
* An application need never use Temporary directly, save to
* call Temporary::wrap() inside loadTasks or loadShortcuts, and
* to call Temporary::complete() immediately prior to terminating.
* This is recommended, but not required; this function will be
* registered as a shutdown function, and called on termination.
*/
class Temporary
{
private static $collection;
/**
* Provides direct access to the collection of temporaries, if necessary.
*/
public static function getCollection()
{
if (!static::$collection) {
static::$collection = \Robo\Robo::getContainer()->get('collection');
register_shutdown_function(function () {
static::complete();
});
}
return static::$collection;
}
/**
* Call the complete method of all of the registered objects.
*/
public static function complete()
{
// Run the collection of tasks. This will also run the
// completion tasks.
$collection = static::getCollection();
$collection->run();
// Make sure that our completion functions do not run twice.
$collection->reset();
}
}
<?php
namespace Robo\Common;
use Robo\Robo;
use Robo\Collection\CollectionBuilder;
trait BuilderAwareTrait
{
/**
* @var \Robo\Collection\CollectionBuilder
*/
protected $builder;
/**
* @see \Robo\Contract\BuilderAwareInterface::setBuilder()
*
* @param \Robo\Collection\CollectionBuilder $builder
*
* @return $this
*/
public function setBuilder(CollectionBuilder $builder)
{
$this->builder = $builder;
return $this;
}
/**
* @see \Robo\Contract\BuilderAwareInterface::getBuilder()
*
* @return \Robo\Collection\CollectionBuilder
*/
public function getBuilder()
{
return $this->builder;
}
/**
* @return \Robo\Collection\CollectionBuilder
*/
protected function collectionBuilder()
{
return $this->getBuilder()->newBuilder();
}
}
<?php
namespace Robo\Common;
use Symfony\Component\Process\ProcessUtils;
/**
* Use this to add arguments and options to the $arguments property.
*/
trait CommandArguments
{
/**
* @var string
*/
protected $arguments = '';
/**
* Pass argument to executable. Its value will be automatically escaped.
*
* @param string $arg
*
* @return $this
*/
public function arg($arg)
{
return $this->args($arg);
}
/**
* Pass methods parameters as arguments to executable. Argument values
* are automatically escaped.
*
* @param string|string[] $args
*
* @return $this
*/
public function args($args)
{
if (!is_array($args)) {
$args = func_get_args();
}
$this->arguments .= ' ' . implode(' ', array_map('static::escape', $args));
return $this;
}
/**
* Pass the provided string in its raw (as provided) form as an argument to executable.
*
* @param string $arg
*/
public function rawArg($arg)
{
$this->arguments .= " $arg";
}
/**
* Escape the provided value, unless it contains only alphanumeric
* plus a few other basic characters.
*
* @param string $value
*
* @return string
*/
public static function escape($value)
{
if (preg_match('/^[a-zA-Z0-9\/\.@~_-]+$/', $value)) {
return $value;
}
return ProcessUtils::escapeArgument($value);
}
/**
* Pass option to executable. Options are prefixed with `--` , value can be provided in second parameter.
* Option values are automatically escaped.
*
* @param string $option
* @param string $value
*
* @return $this
*/
public function option($option, $value = null)
{
if ($option !== null and strpos($option, '-') !== 0) {
$option = "--$option";
}
$this->arguments .= null == $option ? '' : " " . $option;
$this->arguments .= null == $value ? '' : " " . static::escape($value);
return $this;
}
/**
* Pass multiple options to executable. Value can be a string or array.
* Option values are automatically escaped.
*
* @param string $option
* @param string|array $value
*
* @return $this
*/
public function optionList($option, $value = array())
{
if (is_array($value)) {
foreach ($value as $item) {
$this->optionList($option, $item);
}
} else {
$this->option($option, $value);
}
return $this;
}
}
<?php
namespace Robo\Common;
use Robo\Contract\CommandInterface;
use Robo\Exception\TaskException;
/**
* This task can receive commands from task implementing CommandInterface.
*/
trait CommandReceiver
{
/**
* @param string|\Robo\Contract\CommandInterface $command
*
* @return string
*
* @throws \Robo\Exception\TaskException
*/
protected function receiveCommand($command)
{
if (!is_object($command)) {
return $command;
}
if ($command instanceof CommandInterface) {
return $command->getCommand();
} else {
throw new TaskException($this, get_class($command) . " does not implement CommandInterface, so can't be passed into this task");
}
}
}
<?php
namespace Robo\Common;
use Robo\Robo;
use Robo\Config;
trait ConfigAwareTrait
{
/**
* @var \Robo\Config
*/
protected $config;
/**
* Set the config management object.
*
* @param \Robo\Config $config
*
* @return $this
*
* @see \Robo\Contract\ConfigAwareInterface::setConfig()
*/
public function setConfig(Config $config)
{
$this->config = $config;
return $this;
}
/**
* Get the config management object.
*
* @return \Robo\Config
*
* @see \Robo\Contract\ConfigAwareInterface::getConfig()
*/
public function getConfig()
{
return $this->config;
}
/**
* @param string $key
*
* @return string
*/
private static function getClassKey($key)
{
return sprintf('%s.%s', get_called_class(), $key);
}
/**
* @param string $key
* @param mixed $value
*
* @deprecated
*/
public static function configure($key, $value)
{
Robo::config()->set(static::getClassKey($key), $value);
}
/**
* @param string $key
* @param mixed|null $default
*
* @return mixed|null
*/
protected function getConfigValue($key, $default = null)
{
if (!$this->getConfig()) {
return $default;
}
return $this->getConfig()->get(static::getClassKey($key), $default);
}
}
<?php
namespace Robo\Common;
/**
* Simplifies generating of configuration chanined methods.
* You can only define configuration properties and use magic methods to set them.
* Methods will be named the same way as properties.
* * Boolean properties are switched on/off if no values is provided.
* * Array properties can accept non-array values, in this case value will be appended to array.
* You should also define phpdoc for methods.
*/
trait DynamicParams
{
/**
* @param string $property
* @param array $args
*
* @return $this
*/
public function __call($property, $args)
{
if (!property_exists($this, $property)) {
throw new \RuntimeException("Property $property in task ".get_class($this).' does not exists');
}
// toggle boolean values
if (!isset($args[0]) and (is_bool($this->$property))) {
$this->$property = !$this->$property;
return $this;
}
// append item to array
if (is_array($this->$property)) {
if (is_array($args[0])) {
$this->$property = $args[0];
} else {
array_push($this->$property, $args[0]);
}
return $this;
}
$this->$property = $args[0];
return $this;
}
}
<?php
namespace Robo\Common;
use Robo\Result;
use Symfony\Component\Process\ExecutableFinder;
use Symfony\Component\Process\Process;
/**
* This task is supposed to be executed as shell command.
* You can specify working directory and if output is printed.
*/
trait ExecCommand
{
/**
* @var bool
*/
protected $isPrinted = true;
/**
* @var string
*/
protected $workingDirectory;
/**
* @var \Robo\Common\TimeKeeper
*/
protected $execTimer;
/**
* @return \Robo\Common\TimeKeeper
*/
protected function getExecTimer()
{
if (!isset($this->execTimer)) {
$this->execTimer = new TimeKeeper();
}
return $this->execTimer;
}
/**
* Is command printing its output to screen
*
* @return bool
*/
public function getPrinted()
{
return $this->isPrinted;
}
/**
* Changes working directory of command
*
* @param string $dir
*
* @return $this
*/
public function dir($dir)
{
$this->workingDirectory = $dir;
return $this;
}
/**
* Should command output be printed
*
* @param bool $arg
*
* @return $this
*/
public function printed($arg)
{
if (is_bool($arg)) {
$this->isPrinted = $arg;
}
return $this;
}
/**
* Look for a "{$cmd}.phar" in the current working
* directory; return a string to exec it if it is
* found. Otherwise, look for an executable command
* of the same name via findExecutable.
*
* @param string $cmd
*
* @return bool|string
*/
protected function findExecutablePhar($cmd)
{
if (file_exists("{$cmd}.phar")) {
return "php {$cmd}.phar";
}
return $this->findExecutable($cmd);
}
/**
* Return the best path to the executable program
* with the provided name. Favor vendor/bin in the
* current project. If not found there, use
* whatever is on the $PATH.
*
* @param string $cmd
*
* @return bool|string
*/
protected function findExecutable($cmd)
{
$pathToCmd = $this->searchForExecutable($cmd);
if ($pathToCmd) {
return $this->useCallOnWindows($pathToCmd);
}
return false;
}
/**
* @param string $cmd
*
* @return string
*/
private function searchForExecutable($cmd)
{
$projectBin = $this->findProjectBin();
$localComposerInstallation = $projectBin . DIRECTORY_SEPARATOR . $cmd;
if (file_exists($localComposerInstallation)) {
return $localComposerInstallation;
}
$finder = new ExecutableFinder();
return $finder->find($cmd, null, []);
}
/**
* @return bool|string
*/
protected function findProjectBin()
{
$candidates = [ __DIR__ . '/../../vendor/bin', __DIR__ . '/../../bin' ];
// If this project is inside a vendor directory, give highest priority
// to that directory.
$vendorDirContainingUs = realpath(__DIR__ . '/../../../..');
if (is_dir($vendorDirContainingUs) && (basename($vendorDirContainingUs) == 'vendor')) {
array_unshift($candidates, $vendorDirContainingUs . '/bin');
}
foreach ($candidates as $dir) {
if (is_dir("$dir")) {
return realpath($dir);
}
}
return false;
}
/**
* Wrap Windows executables in 'call' per 7a88757d
*
* @param string $cmd
*
* @return string
*/
protected function useCallOnWindows($cmd)
{
if (defined('PHP_WINDOWS_VERSION_BUILD')) {
if (file_exists("{$cmd}.bat")) {
$cmd = "{$cmd}.bat";
}
return "call $cmd";
}
return $cmd;
}
/**
* @param string $command
*
* @return \Robo\Result
*/
protected function executeCommand($command)
{
$process = new Process($command);
$process->setTimeout(null);
if ($this->workingDirectory) {
$process->setWorkingDirectory($this->workingDirectory);
}
$this->getExecTimer()->start();
if ($this->isPrinted) {
$process->run(function ($type, $buffer) {
print $buffer;
});
} else {
$process->run();
}
$this->getExecTimer()->stop();
return new Result($this, $process->getExitCode(), $process->getOutput(), ['time' => $this->getExecTimer()->elapsed()]);
}
}
<?php
namespace Robo\Common;
/**
* This task specifies exactly one shell command.
* It can take additional arguments and options as config parameters.
*/
trait ExecOneCommand
{
use ExecCommand;
use CommandArguments;
}
<?php
namespace Robo\Common;
use Robo\Contract\InflectionInterface;
trait InflectionTrait
{
/**
* Ask the provided parent class to inject all of the dependencies
* that it has and we need.
*
* @param \Robo\Contract\InflectionInterface $parent
*
* @return $this
*/
public function inflect(InflectionInterface $parent)
{
$parent->injectDependencies($this);
return $this;
}
}
<?php
namespace Robo\Common;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Input\InputInterface;
trait InputAwareTrait
{
/**
* @var \Symfony\Component\Console\Input\InputInterface
*/
protected $input;
/**
* @param \Symfony\Component\Console\Input\InputInterface $input
*
* @return $this
*
* @see \Symfony\Component\Console\Input\InputAwareInterface::setInput()
*/
public function setInput(InputInterface $input)
{
$this->input = $input;
return $this;
}
/**
* @return \Symfony\Component\Console\Input\InputInterface
*/
protected function input()
{
if (!isset($this->input)) {
$this->setInput(new ArgvInput());
}
return $this->input;
}
/**
* Backwards compatibility.
*
* @return \Symfony\Component\Console\Input\InputInterface
*
* @deprecated
*/
protected function getInput()
{
return $this->input();
}
}
<?php
namespace Robo\Common;
use Robo\Robo;
use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\NullOutput;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ConfirmationQuestion;
use Symfony\Component\Console\Question\Question;
use Symfony\Component\Console\Style\SymfonyStyle;
trait IO
{
use InputAwareTrait;
use OutputAwareTrait;
/**
* @var \Symfony\Component\Console\Style\SymfonyStyle
*/
protected $io;
/**
* Provide access to SymfonyStyle object.
*
* @return SymfonyStyle
*
* @see http://symfony.com/blog/new-in-symfony-2-8-console-style-guide
*/
protected function io()
{
if (!$this->io) {
$this->io = new SymfonyStyle($this->input(), $this->output());
}
return $this->io;
}
/**
* @param string $nonDecorated
* @param string $decorated
*
* @return string
*/
protected function decorationCharacter($nonDecorated, $decorated)
{
if (!$this->output()->isDecorated() || (strncasecmp(PHP_OS, 'WIN', 3) == 0)) {
return $nonDecorated;
}
return $decorated;
}
/**
* @param string $text
*/
protected function say($text)
{
$char = $this->decorationCharacter('>', '➜');
$this->writeln("$char $text");
}
/**
* @param string $text
* @param int $length
* @param string $color
*/
protected function yell($text, $length = 40, $color = 'green')
{
$char = $this->decorationCharacter(' ', '➜');
$format = "$char <fg=white;bg=$color;options=bold>%s</fg=white;bg=$color;options=bold>";
$this->formattedOutput($text, $length, $format);
}
/**
* @param string $text
* @param int $length
* @param string $format
*/
private function formattedOutput($text, $length, $format)
{
$lines = explode("\n", trim($text, "\n"));
$maxLineLength = array_reduce(array_map('strlen', $lines), 'max');
$length = max($length, $maxLineLength);
$len = $length + 2;
$space = str_repeat(' ', $len);
$this->writeln(sprintf($format, $space));
foreach ($lines as $line) {
$line = str_pad($line, $length, ' ', STR_PAD_BOTH);
$this->writeln(sprintf($format, " $line "));
}
$this->writeln(sprintf($format, $space));
}
/**
* @param string $question
* @param bool $hideAnswer
*
* @return string
*/
protected function ask($question, $hideAnswer = false)
{
if ($hideAnswer) {
return $this->askHidden($question);
}
return $this->doAsk(new Question($this->formatQuestion($question)));
}
/**
* @param string $question
*
* @return string
*/
protected function askHidden($question)
{
$question = new Question($this->formatQuestion($question));
$question->setHidden(true);
return $this->doAsk($question);
}
/**
* @param string $question
* @param string $default
*
* @return string
*/
protected function askDefault($question, $default)
{
return $this->doAsk(new Question($this->formatQuestion("$question [$default]"), $default));
}
/**
* @param string $question
*
* @return string
*/
protected function confirm($question)
{
return $this->doAsk(new ConfirmationQuestion($this->formatQuestion($question . ' (y/n)'), false));
}
/**
* @param \Symfony\Component\Console\Question\Question $question
*
* @return string
*/
private function doAsk(Question $question)
{
return $this->getDialog()->ask($this->input(), $this->output(), $question);
}
/**
* @param string $message
*
* @return string
*/
private function formatQuestion($message)
{
return "<question>? $message</question> ";
}
/**
* @return \Symfony\Component\Console\Helper\QuestionHelper
*/
protected function getDialog()
{
return new QuestionHelper();
}
/**
* @param $text
*/
private function writeln($text)
{
$this->output()->writeln($text);
}
}
<?php
namespace Robo\Common;
use Symfony\Component\Console\Output\NullOutput;
use Symfony\Component\Console\Output\OutputInterface;
trait OutputAwareTrait
{
/**
* @var \Symfony\Component\Console\Output\OutputInterface
*/
protected $output;
/**
* @param \Symfony\Component\Console\Output\OutputInterface $output
*
* @return $this
*
* @see \Robo\Contract\OutputAwareInterface::setOutput()
*/
public function setOutput(OutputInterface $output)
{
$this->output = $output;
return $this;
}
/**
* @return \Symfony\Component\Console\Output\OutputInterface
*/
protected function output()
{
if (!isset($this->output)) {
$this->setOutput(new NullOutput());
}
return $this->output;
}
/**
* Backwards compatibility
*
* @return \Symfony\Component\Console\Output\OutputInterface
*
* @deprecated
*/
protected function getOutput()
{
return $this->output();
}
}
<?php
namespace Robo\Common;
/**
* Wrapper around \Symfony\Component\Console\Helper\ProgressBar
*/
class ProgressIndicator
{
use Timer;
/**
* @var \Symfony\Component\Console\Helper\ProgressBar
*/
protected $progressBar;
/**
* @var \Symfony\Component\Console\Output\OutputInterface
*/
protected $output;
/**
* @var bool
*/
protected $progressIndicatorRunning = false;
/**
* @var int
*/
protected $autoDisplayInterval = 0;
/**
* @var int
*/
protected $cachedSteps = 0;
/**
* @var int
*/
protected $totalSteps = 0;
/**
* @var bool
*/
protected $progressBarDisplayed = false;
/**
* @var \Robo\Contract\TaskInterface
*/
protected $owner;
/**
* @param \Symfony\Component\Console\Helper\ProgressBar $progressBar
* @param \Symfony\Component\Console\Output\OutputInterface $output
*/
public function __construct($progressBar, \Symfony\Component\Console\Output\OutputInterface $output)
{
$this->progressBar = $progressBar;
$this->output = $output;
}
/**
* @param int $interval
*/
public function setProgressBarAutoDisplayInterval($interval)
{
if ($this->progressIndicatorRunning) {
return;
}
$this->autoDisplayInterval = $interval;
}
/**
* @return bool
*/
public function hideProgressIndicator()
{
$result = $this->progressBarDisplayed;
if ($this->progressIndicatorRunning && $this->progressBarDisplayed) {
$this->progressBar->clear();
// Hack: progress indicator does not reset cursor to beginning of line on 'clear'
$this->output->write("\x0D");
$this->progressBarDisplayed = false;
}
return $result;
}
public function showProgressIndicator()
{
if ($this->progressIndicatorRunning && !$this->progressBarDisplayed && isset($this->progressBar)) {
$this->progressBar->display();
$this->progressBarDisplayed = true;
$this->advanceProgressIndicatorCachedSteps();
}
}
/**
* @param bool $visible
*/
public function restoreProgressIndicator($visible)
{
if ($visible) {
$this->showProgressIndicator();
}
}
/**
* @param int $totalSteps
* @param \Robo\Contract\TaskInterface $owner
*/
public function startProgressIndicator($totalSteps, $owner)
{
if (!isset($this->progressBar)) {
return;
}
$this->progressIndicatorRunning = true;
if (!isset($this->owner)) {
$this->owner = $owner;
$this->startTimer();
$this->totalSteps = $totalSteps;
$this->autoShowProgressIndicator();
}
}
public function autoShowProgressIndicator()
{
if (($this->autoDisplayInterval < 0) || !isset($this->progressBar) || !$this->output->isDecorated()) {
return;
}
if ($this->autoDisplayInterval <= $this->getExecutionTime()) {
$this->autoDisplayInterval = -1;
$this->progressBar->start($this->totalSteps);
$this->showProgressIndicator();
}
}
/**
* @return bool
*/
public function inProgress()
{
return $this->progressIndicatorRunning;
}
/**
* @param \Robo\Contract\TaskInterface $owner
*/
public function stopProgressIndicator($owner)
{
if ($this->progressIndicatorRunning && ($this->owner === $owner)) {
$this->cleanup();
}
}
protected function cleanup()
{
$this->progressIndicatorRunning = false;
$this->owner = null;
if ($this->progressBarDisplayed) {
$this->progressBar->finish();
// Hack: progress indicator does not always finish cleanly
$this->output->writeln('');
$this->progressBarDisplayed = false;
}
$this->stopTimer();
}
/**
* Erase progress indicator and ensure it never returns. Used
* only during error handlers.
*/
public function disableProgressIndicator()
{
$this->cleanup();
// ProgressIndicator is shared, so this permanently removes
// the program's ability to display progress bars.
$this->progressBar = null;
}
/**
* @param int $steps
*/
public function advanceProgressIndicator($steps = 1)
{
$this->cachedSteps += $steps;
if ($this->progressIndicatorRunning) {
$this->autoShowProgressIndicator();
// We only want to call `advance` if the progress bar is visible,
// because it always displays itself when it is advanced.
if ($this->progressBarDisplayed) {
return $this->advanceProgressIndicatorCachedSteps();
}
}
}
protected function advanceProgressIndicatorCachedSteps()
{
$this->progressBar->advance($this->cachedSteps);
$this->cachedSteps = 0;
}
}
<?php
namespace Robo\Common;
trait ProgressIndicatorAwareTrait
{
use Timer;
/**
* @var null|\Robo\Common\ProgressIndicator
*/
protected $progressIndicator;
/**
* @return int
*/
public function progressIndicatorSteps()
{
return 0;
}
/**
* @param null|\Robo\Common\ProgressIndicator $progressIndicator
*/
public function setProgressIndicator($progressIndicator)
{
$this->progressIndicator = $progressIndicator;
}
/**
* @return null|bool
*/
protected function hideProgressIndicator()
{
if (!$this->progressIndicator) {
return;
}
return $this->progressIndicator->hideProgressIndicator();
}
protected function showProgressIndicator()
{
if (!$this->progressIndicator) {
return;
}
$this->progressIndicator->showProgressIndicator();
}
/**
* @param bool $visible
*/
protected function restoreProgressIndicator($visible)
{
if (!$this->progressIndicator) {
return;
}
$this->progressIndicator->restoreProgressIndicator($visible);
}
/**
* @return int
*/
protected function getTotalExecutionTime()
{
if (!$this->progressIndicator) {
return 0;
}
return $this->progressIndicator->getExecutionTime();
}
protected function startProgressIndicator()
{
$this->startTimer();
if (!$this->progressIndicator) {
return;
}
$totalSteps = $this->progressIndicatorSteps();
$this->progressIndicator->startProgressIndicator($totalSteps, $this);
}
/**
* @return bool
*/
protected function inProgress()
{
if (!$this->progressIndicator) {
return false;
}
return $this->progressIndicator->inProgress();
}
protected function stopProgressIndicator()
{
$this->stopTimer();
if (!$this->progressIndicator) {
return;
}
$this->progressIndicator->stopProgressIndicator($this);
}
protected function disableProgressIndicator()
{
$this->stopTimer();
if (!$this->progressIndicator) {
return;
}
$this->progressIndicator->disableProgressIndicator();
}
protected function detatchProgressIndicator()
{
$this->setProgressIndicator(null);
}
/**
* @param int $steps
*/
protected function advanceProgressIndicator($steps = 1)
{
if (!$this->progressIndicator) {
return;
}
$this->progressIndicator->advanceProgressIndicator($steps);
}
}
<?php
namespace Robo\Common;
trait ResourceExistenceChecker
{
/**
* Checks if the given input is a file or folder.
*
* @param string|string[] $resources
* @param string $type "file", "dir", "fileAndDir"
*
* @return bool True if no errors were encountered otherwise false.
*/
protected function checkResources($resources, $type = 'fileAndDir')
{
if (!in_array($type, ['file', 'dir', 'fileAndDir'])) {
throw new \InvalidArgumentException(sprintf('Invalid resource check of type "%s" used!', $type));
}
if (is_string($resources)) {
$resources = [$resources];
}
$success = true;
foreach ($resources as $resource) {
$glob = glob($resource);
if ($glob === false) {
$this->printTaskError(sprintf('Invalid glob "%s"!', $resource), $this);
$success = false;
continue;
}
foreach ($glob as $resource) {
if (!$this->checkResource($resource, $type)) {
$success = false;
}
}
}
return $success;
}
/**
* Checks a single resource, file or directory.
*
* It will print an error as well on the console.
*
* @param string $resource File or folder.
* @param string $type "file", "dir", "fileAndDir"
*
* @return bool
*/
protected function checkResource($resource, $type)
{
switch ($type) {
case 'file':
if (!$this->isFile($resource)) {
$this->printTaskError(sprintf('File "%s" does not exist!', $resource), $this);
return false;
}
return true;
case 'dir':
if (!$this->isDir($resource)) {
$this->printTaskError(sprintf('Directory "%s" does not exist!', $resource), $this);
return false;
}
return true;
case 'fileAndDir':
if (!$this->isDir($resource) && !$this->isFile($resource)) {
$this->printTaskError(sprintf('File or directory "%s" does not exist!', $resource), $this);
return false;
}
return true;
}
}
/**
* Convenience method to check the often uses "source => target" file / folder arrays.
*
* @param string|array $resources
*/
protected function checkSourceAndTargetResource($resources)
{
if (is_string($resources)) {
$resources = [$resources];
}
$sources = [];
$targets = [];
foreach ($resources as $source => $target) {
$sources[] = $source;
$target[] = $target;
}
$this->checkResources($sources);
$this->checkResources($targets);
}
/**
* Wrapper method around phps is_dir()
*
* @param string $directory
*
* @return bool
*/
protected function isDir($directory)
{
return is_dir($directory);
}
/**
* Wrapper method around phps file_exists()
*
* @param string $file
*
* @return bool
*/
protected function isFile($file)
{
return file_exists($file);
}
}
<?php
namespace Robo\Common;
use Robo\Robo;
use Robo\TaskInfo;
use Consolidation\Log\ConsoleLogLevel;
use Robo\Common\ConfigAwareTrait;
use Psr\Log\LoggerAwareTrait;
use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
use Robo\Contract\ProgressIndicatorAwareInterface;
/**
* Task input/output methods. TaskIO is 'used' in BaseTask, so any
* task that extends this class has access to all of the methods here.
* printTaskInfo, printTaskSuccess, and printTaskError are the three
* primary output methods that tasks are encouraged to use. Tasks should
* avoid using the IO trait output methods.
*/
trait TaskIO
{
use LoggerAwareTrait;
use ConfigAwareTrait;
/**
* @return mixed|null|\Psr\Log\LoggerInterface
*/
public function logger()
{
// $this->logger should always be set in Robo core tasks.
if ($this->logger) {
return $this->logger;
}
// TODO: Remove call to Robo::logger() once maintaining backwards
// compatibility with legacy external Robo tasks is no longer desired.
if (!Robo::logger()) {
return null;
}
static $gaveDeprecationWarning = false;
if (!$gaveDeprecationWarning) {
trigger_error('No logger set for ' . get_class($this) . '. Use $this->task(Foo::class) rather than new Foo() in loadTasks to ensure the builder can initialize task the task, or use $this->collectionBuilder()->taskFoo() if creating one task from within another.', E_USER_DEPRECATED);
$gaveDeprecationWarning = true;
}
return Robo::logger();
}
/**
* Print information about a task in progress.
*
* With the Symfony Console logger, NOTICE is displayed at VERBOSITY_VERBOSE
* and INFO is displayed at VERBOSITY_VERY_VERBOSE.
*
* Robo overrides the default such that NOTICE is displayed at
* VERBOSITY_NORMAL and INFO is displayed at VERBOSITY_VERBOSE.
*
* n.b. We should probably have printTaskNotice for our ordinary
* output, and use printTaskInfo for less interesting messages.
*
* @param string $text
* @param null|array $context
*/
protected function printTaskInfo($text, $context = null)
{
// The 'note' style is used for both 'notice' and 'info' log levels;
// However, 'notice' is printed at VERBOSITY_NORMAL, whereas 'info'
// is only printed at VERBOSITY_VERBOSE.
$this->printTaskOutput(LogLevel::NOTICE, $text, $this->getTaskContext($context));
}
/**
* Provide notification that some part of the task succeeded.
*
* With the Symfony Console logger, success messages are remapped to NOTICE,
* and displayed in VERBOSITY_VERBOSE. When used with the Robo logger,
* success messages are displayed at VERBOSITY_NORMAL.
*
* @param string $text
* @param null|array $context
*/
protected function printTaskSuccess($text, $context = null)
{
// Not all loggers will recognize ConsoleLogLevel::SUCCESS.
// We therefore log as LogLevel::NOTICE, and apply a '_level'
// override in the context so that this message will be
// logged as SUCCESS if that log level is recognized.
$context['_level'] = ConsoleLogLevel::SUCCESS;
$this->printTaskOutput(LogLevel::NOTICE, $text, $this->getTaskContext($context));
}
/**
* Provide notification that there is something wrong, but
* execution can continue.
*
* Warning messages are displayed at VERBOSITY_NORMAL.
*
* @param string $text
* @param null|array $context
*/
protected function printTaskWarning($text, $context = null)
{
$this->printTaskOutput(LogLevel::WARNING, $text, $this->getTaskContext($context));
}
/**
* Provide notification that some operation in the task failed,
* and the task cannot continue.
*
* Error messages are displayed at VERBOSITY_NORMAL.
*
* @param string $text
* @param null|array $context
*/
protected function printTaskError($text, $context = null)
{
$this->printTaskOutput(LogLevel::ERROR, $text, $this->getTaskContext($context));
}
/**
* Provide debugging notification. These messages are only
* displayed if the log level is VERBOSITY_DEBUG.
*
* @param string$text
* @param null|array $context
*/
protected function printTaskDebug($text, $context = null)
{
$this->printTaskOutput(LogLevel::DEBUG, $text, $this->getTaskContext($context));
}
/**
* @param string $level
* One of the \Psr\Log\LogLevel constant
* @param string $text
* @param null|array $context
*/
protected function printTaskOutput($level, $text, $context)
{
$logger = $this->logger();
if (!$logger) {
return;
}
// Hide the progress indicator, if it is visible.
$inProgress = $this->hideTaskProgress();
$logger->log($level, $text, $this->getTaskContext($context));
// After we have printed our log message, redraw the progress indicator.
$this->showTaskProgress($inProgress);
}
/**
* @return bool
*/
protected function hideTaskProgress()
{
$inProgress = false;
if ($this instanceof ProgressIndicatorAwareInterface) {
$inProgress = $this->inProgress();
}
// If a progress indicator is running on this task, then we mush
// hide it before we print anything, or its display will be overwritten.
if ($inProgress) {
$inProgress = $this->hideProgressIndicator();
}
return $inProgress;
}
/**
* @param $inProgress
*/
protected function showTaskProgress($inProgress)
{
if ($inProgress) {
$this->restoreProgressIndicator($inProgress);
}
}
/**
* Format a quantity of bytes.
*
* @param int $size
* @param int $precision
*
* @return string
*/
protected function formatBytes($size, $precision = 2)
{
if ($size === 0) {
return 0;
}
$base = log($size, 1024);
$suffixes = array('', 'k', 'M', 'G', 'T');
return round(pow(1024, $base - floor($base)), $precision) . $suffixes[floor($base)];
}
/**
* Get the formatted task name for use in task output.
* This is placed in the task context under 'name', and
* used as the log label by Robo\Common\RoboLogStyle,
* which is inserted at the head of log messages by
* Robo\Common\CustomLogStyle::formatMessage().
*
* @param null|object $task
*
* @return string
*/
protected function getPrintedTaskName($task = null)
{
if (!$task) {
$task = $this;
}
return TaskInfo::formatTaskName($task);
}
/**
* @param null|array $context
*
* @return array with context information
*/
protected function getTaskContext($context = null)
{
if (!$context) {
$context = [];
}
if (!is_array($context)) {
$context = ['task' => $context];
}
if (!array_key_exists('task', $context)) {
$context['task'] = $this;
}
return $context + TaskInfo::getTaskContext($context['task']);
}
}
<?php
namespace Robo\Common;
class TimeKeeper
{
const MINUTE = 60;
const HOUR = 3600;
const DAY = 86400;
/**
* @var float
*/
protected $startedAt;
/**
* @var float
*/
protected $finishedAt;
public function start()
{
if ($this->startedAt) {
return;
}
// Get time in seconds as a float, accurate to the microsecond.
$this->startedAt = microtime(true);
}
public function stop()
{
$this->finishedAt = microtime(true);
}
/**
* @return float|null
*/
public function elapsed()
{
$finished = $this->finishedAt ? $this->finishedAt : microtime(true);
if ($finished - $this->startedAt <= 0) {
return null;
}
return $finished - $this->startedAt;
}
/**
* Format a duration into a human-readable time
*
* @param float $duration Duration in seconds, with fractional component
*
* @return string
*/
public static function formatDuration($duration)
{
if ($duration >= self::DAY * 2) {
return gmdate('z \d\a\y\s H:i:s', $duration);
}
if ($duration > self::DAY) {
return gmdate('\1 \d\a\y H:i:s', $duration);
}
if ($duration > self::HOUR) {
return gmdate("H:i:s", $duration);
}
if ($duration > self::MINUTE) {
return gmdate("i:s", $duration);
}
return round($duration, 3).'s';
}
}
<?php
namespace Robo\Common;
trait Timer
{
/**
* @var \Robo\Common\TimeKeeper
*/
protected $timer;
protected function startTimer()
{
if (!isset($this->timer)) {
$this->timer = new TimeKeeper();
}
$this->timer->start();
}
protected function stopTimer()
{
if (!isset($this->timer)) {
return;
}
$this->timer->stop();
}
/**
* @return float|null
*/
protected function getExecutionTime()
{
if (!isset($this->timer)) {
return null;
}
return $this->timer->elapsed();
}
}
<?php
namespace Robo;
class Config
{
const PROGRESS_BAR_AUTO_DISPLAY_INTERVAL = 'progress-delay';
const DEFAULT_PROGRESS_DELAY = 2;
const SIMULATE = 'simulate';
const DECORATED = 'decorated';
/**
* @var array
*/
protected $config = [];
/**
* Fet a configuration value
*
* @param string $key Which config item to look up
* @param string|null $defaultOverride Override usual default value with a different default
*
* @return mixed
*/
public function get($key, $defaultOverride = null)
{
if (isset($this->config[$key])) {
return $this->config[$key];
}
return $this->getDefault($key, $defaultOverride);
}
/**
* Set a config value
*
* @param string $key
* @param mixed $value
*
* @return $this
*/
public function set($key, $value)
{
$this->config[$key] = $value;
return $this;
}
/**
* Return an associative array containing all of the global configuration
* options and their default values.
*
* @return array
*/
public function getGlobalOptionDefaultValues()
{
$globalOptions =
[
self::PROGRESS_BAR_AUTO_DISPLAY_INTERVAL => self::DEFAULT_PROGRESS_DELAY,
self::SIMULATE => false,
];
return $globalOptions;
}
/**
* Return the default value for a given configuration item.
*
* @param string $key
* @param mixed $defaultOverride
*
* @return mixed
*/
public function getDefault($key, $defaultOverride = null)
{
$globalOptions = $this->getGlobalOptionDefaultValues();
return isset($globalOptions[$key]) ? $globalOptions[$key] : $defaultOverride;
}
/**
* @return bool
*/
public function isSimulated()
{
return $this->get(self::SIMULATE);
}
/**
* @param bool $simulated
*
* @return $this
*/
public function setSimulated($simulated = true)
{
return $this->set(self::SIMULATE, $simulated);
}
/**
* @return bool
*/
public function isDecorated()
{
return $this->get(self::DECORATED);
}
/**
* @param bool $decorated
*
* @return $this
*/
public function setDecorated($decorated = true)
{
return $this->set(self::DECORATED, $decorated);
}
/**
* @param int $interval
*
* @return $this
*/
public function setProgressBarAutoDisplayInterval($interval)
{
return $this->set(self::PROGRESS_BAR_AUTO_DISPLAY_INTERVAL, $interval);
}
}
<?php
namespace Robo\Contract;
use Robo\Collection\CollectionBuilder;
interface BuilderAwareInterface
{
/**
* Set the builder reference
*
* @param \Robo\Collection\CollectionBuilder $builder
*/
public function setBuilder(CollectionBuilder $builder);
/**
* Get the builder reference
*
* @return \Robo\Collection\CollectionBuilder
*/
public function getBuilder();
}
<?php
namespace Robo\Contract;
/**
* Task that implements this interface can be injected as a parameter for other task.
* This task can be represented as executable command.
*
* @package Robo\Contract
*/
interface CommandInterface
{
/**
* Returns command that can be executed.
* This method is used to pass generated command from one task to another.
*
* @return string
*/
public function getCommand();
}
<?php
namespace Robo\Contract;
/**
* Any Robo tasks that implements this interface will
* be called when the task collection it is added to
* completes.
*
* Interface CompletionInterface
* @package Robo\Contract
*/
interface CompletionInterface extends TaskInterface
{
/**
* Revert an operation that can be rolled back
*/
public function complete();
}
<?php
namespace Robo\Contract;
use Robo\Config;
interface ConfigAwareInterface
{
/**
* Set the config reference
*
* @param \Robo\Config $config
*
* @return $this
*/
public function setConfig(Config $config);
/**
* Get the config reference
*
* @return \Robo\Config
*/
public function getConfig();
}
<?php
namespace Robo\Contract;
interface InflectionInterface
{
/**
* Based on league/container inflection: http://container.thephpleague.com/inflectors/
*
* This allows us to run:
*
* (new SomeTask($args))
* ->inflect($this)
* ->initializer()
* ->...
*
* Instead of:
*
* (new SomeTask($args))
* ->setLogger($this->logger)
* ->initializer()
* ->...
*
* The reason `inflect` is better than the more explicit alternative is
* that subclasses of BaseTask that implement a new FooAwareInterface
* can override injectDependencies() as explained below, and add more
* dependencies that can be injected as needed.
*
* @param \Robo\Contract\InflectionInterface $parent
*/
public function inflect(InflectionInterface $parent);
/**
* Take all dependencies availble to this task and inject any that are
* needed into the provided task. The general pattern is that, for every
* FooAwareInterface that this class implements, it should test to see
* if the child also implements the same interface, and if so, should call
* $child->setFoo($this->foo).
*
* The benefits of this are pretty large. Any time an object that implements
* InflectionInterface is created, just call `$child->inflect($this)`, and
* any available optional dependencies will be hooked up via setter injection.
*
* The required dependencies of an object should be provided via constructor
* injection, not inflection.
*
* @param InflectionInterface $child An object created by this class that
* should have its dependencies injected.
*
* @see https://mwop.net/blog/2016-04-26-on-locators.html
*/
public function injectDependencies(InflectionInterface $child);
}
<?php
/**
* Marker interface for tasks that use the IO trait
*/
namespace Robo\Contract;
use \Symfony\Component\Console\Input\InputAwareInterface;
interface IOAwareInterface extends OutputAwareInterface, InputAwareInterface
{
}
<?php
/**
* Provide OutputAwareInterface, not present in Symfony Console
*/
namespace Robo\Contract;
use Symfony\Component\Console\Output\OutputInterface;
interface OutputAwareInterface
{
/**
* Sets the Console Output.
*
* @param \Symfony\Component\Console\Output\OutputInterface $output
*/
public function setOutput(OutputInterface $output);
}
<?php
namespace Robo\Contract;
/**
* If task prints anything to console
*
* Interface PrintedInterface
* @package Robo\Contract
*/
interface PrintedInterface
{
/**
* @return bool
*/
public function getPrinted();
}
<?php
namespace Robo\Contract;
/**
* Any Robo task that uses the Timer trait and
* implements ProgressIndicatorAwareInterface will
* display a progress bar while the timer is running.
* Call advanceProgressIndicator to advance the indicator.
*
* Interface ProgressIndicatorAwareInterface
* @package Robo\Contract
*/
interface ProgressIndicatorAwareInterface
{
/**
* @return int
*/
public function progressIndicatorSteps();
/**
* @param \Robo\Common\ProgressIndicator $progressIndicator
*/
public function setProgressIndicator($progressIndicator);
}
<?php
namespace Robo\Contract;
/**
* Robo tasks that take multiple steps to complete should
* implement this interface.
*
* Interface ProgressInterface
* @package Robo\Contract
*/
interface ProgressInterface
{
/**
*
* @return int
*/
public function progressSteps();
}
<?php
namespace Robo\Contract;
/**
* Any Robo tasks that implements this interface will
* be called when the task collection it is added to
* fails, and runs its rollback operation.
*
* Interface RollbackInterface
* @package Robo\Contract
*/
interface RollbackInterface extends TaskInterface
{
/**
* Revert an operation that can be rolled back
*/
public function rollback();
}
<?php
namespace Robo\Contract;
/**
* Task that implements this interface can be injected as a parameter for other task.
* This task can be represented as executable command.
*
* @package Robo\Contract
*/
interface SimulatedInterface extends TaskInterface
{
/**
* Called in place of `run()` for simulated tasks.
*
* @param null|array $context
*/
public function simulate($context);
}
<?php
namespace Robo\Contract;
/**
* All Robo tasks should implement this interface.
* Task should be configured by chained methods.
*
* Interface TaskInterface
* @package Robo\Contract
*/
interface TaskInterface
{
/**
* @return \Robo\Result
*/
public function run();
}
<?php
namespace Robo\Contract;
interface WrappedTaskInterface extends TaskInterface
{
/**
* @return \Robo\Contract\TaskInterface
*/
public function original();
}
<?php
namespace Robo\Exception;
class TaskException extends \Exception
{
public function __construct($class, $message)
{
if (is_object($class)) {
$class = get_class($class);
}
parent::__construct(" in task $class \n\n $message");
}
}
<?php
namespace Robo\Exception;
class TaskExitException extends \Exception
{
public function __construct($class, $message, $status)
{
if (is_object($class)) {
$class = get_class($class);
}
parent::__construct(" in task $class \n\n $message", $status);
}
}
<?php
namespace Robo;
use Symfony\Component\Console\ConsoleEvents;
use Symfony\Component\Console\Event\ConsoleCommandEvent;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Robo\Contract\ConfigAwareInterface;
use Robo\Common\ConfigAwareTrait;
class GlobalOptionsEventListener implements EventSubscriberInterface, ConfigAwareInterface
{
use ConfigAwareTrait;
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents()
{
return [ConsoleEvents::COMMAND => 'setGlobalOptions'];
}
/**
* Before a Console command runs, examine the global
* commandline options from the event Input, and set
* configuration values as appropriate.
*
* @param \Symfony\Component\Console\Event\ConsoleCommandEvent $event
*/
public function setGlobalOptions(ConsoleCommandEvent $event)
{
$config = $this->getConfig();
$input = $event->getInput();
$globalOptions = $config->getGlobalOptionDefaultValues();
foreach ($globalOptions as $option => $default) {
$value = $input->hasOption($option) ? $input->getOption($option) : null;
// Unfortunately, the `?:` operator does not differentate between `0` and `null`
if (!isset($value)) {
$value = $default;
}
$config->set($option, $value);
}
}
}
<?php
namespace Robo;
trait LoadAllTasks
{
use TaskAccessor;
use Collection\loadTasks;
// standard tasks
use Task\Base\loadTasks;
use Task\Development\loadTasks;
use Task\Filesystem\loadTasks;
use Task\File\loadTasks;
use Task\Archive\loadTasks;
use Task\Vcs\loadTasks;
// package managers
use Task\Composer\loadTasks;
use Task\Bower\loadTasks;
use Task\Npm\loadTasks;
// assets
use Task\Assets\loadTasks;
// 3rd-party tools
use Task\Remote\loadTasks;
use Task\Testing\loadTasks;
use Task\ApiGen\loadTasks;
use Task\Docker\loadTasks;
// task runners
use Task\Gulp\loadTasks;
// shortcuts
use Task\Base\loadShortcuts;
use Task\Filesystem\loadShortcuts;
use Task\Vcs\loadShortcuts;
}
<?php
namespace Robo\Log;
use Robo\Result;
use Robo\TaskInfo;
use Robo\Contract\PrintedInterface;
use Robo\Contract\ProgressIndicatorAwareInterface;
use Robo\Common\ProgressIndicatorAwareTrait;
use Psr\Log\LogLevel;
use Psr\Log\LoggerInterface;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerAwareTrait;
use Consolidation\Log\ConsoleLogLevel;
/**
* Log the creation of Result objects.
*/
class ResultPrinter implements LoggerAwareInterface, ProgressIndicatorAwareInterface
{
use LoggerAwareTrait;
use ProgressIndicatorAwareTrait;
/**
* Log the result of a Robo task.
*
* Returns 'true' if the message is printed, or false if it isn't.
*
* @param \Robo\Result $result
*
* @return bool
*/
public function printResult(Result $result)
{
if (!$result->wasSuccessful()) {
return $this->printError($result);
} else {
return $this->printSuccess($result);
}
}
/**
* Log that we are about to abort due to an error being encountered
* in 'stop on fail' mode.
*
* @param \Robo\Result $result
*/
public function printStopOnFail($result)
{
$this->printMessage(LogLevel::NOTICE, 'Stopping on fail. Exiting....');
$this->printMessage(LogLevel::ERROR, 'Exit Code: {code}', ['code' => $result->getExitCode()]);
}
/**
* Log the result of a Robo task that returned an error.
*
* @param \Robo\Result $result
*
* @return bool
*/
protected function printError(Result $result)
{
$task = $result->getTask();
$context = $result->getContext() + ['timer-label' => 'Time', '_style' => []];
$context['_style']['message'] = '';
$printOutput = true;
if ($task instanceof PrintedInterface) {
$printOutput = !$task->getPrinted();
}
if ($printOutput) {
$this->printMessage(LogLevel::ERROR, "{message}", $context);
}
$this->printMessage(LogLevel::ERROR, 'Exit code {code}', $context);
return true;
}
/**
* Log the result of a Robo task that was successful.
*
* @param \Robo\Result $result
*
* @return bool
*/
protected function printSuccess(Result $result)
{
$task = $result->getTask();
$context = $result->getContext() + ['timer-label' => 'in'];
$time = $result->getExecutionTime();
if ($time) {
$this->printMessage(ConsoleLogLevel::SUCCESS, 'Done', $context);
}
return false;
}
/**
* @param string $level
* @param string $message
* @param array $context
*/
protected function printMessage($level, $message, $context = [])
{
$inProgress = $this->hideProgressIndicator();
$this->logger->log($level, $message, $context);
if ($inProgress) {
$this->restoreProgressIndicator($inProgress);
}
}
}
<?php
namespace Robo\Log;
use Robo\Result;
use Robo\TaskInfo;
use Robo\Contract\PrintedInterface;
use Robo\Contract\LogResultInterface;
use Consolidation\Log\ConsoleLogLevel;
use Consolidation\Log\Logger;
use Psr\Log\LogLevel;
use Psr\Log\AbstractLogger;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Logger\ConsoleLogger;
/**
* Robo's default logger
*/
class RoboLogger extends Logger
{
/**
* @param \Symfony\Component\Console\Output\OutputInterface $output
*/
public function __construct(OutputInterface $output)
{
// In Robo, we use log level 'notice' for messages that appear all
// the time, and 'info' for messages that appear only during verbose
// output. We have no 'very verbose' (-vv) level. 'Debug' is -vvv, as usual.
$roboVerbosityOverrides = [
RoboLogLevel::SIMULATED_ACTION => OutputInterface::VERBOSITY_NORMAL, // Default is "verbose"
LogLevel::NOTICE => OutputInterface::VERBOSITY_NORMAL, // Default is "verbose"
LogLevel::INFO => OutputInterface::VERBOSITY_VERBOSE, // Default is "very verbose"
];
parent::__construct($output, $roboVerbosityOverrides);
}
}
<?php
namespace Robo\Log;
class RoboLogLevel extends \Consolidation\Log\ConsoleLogLevel
{
/**
* Command did something in simulated mode.
* Displayed at VERBOSITY_NORMAL.
*/
const SIMULATED_ACTION = 'simulated';
}
<?php
namespace Robo\Log;
use Robo\Common\TimeKeeper;
use Consolidation\Log\LogOutputStyler;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\OutputStyle;
/**
* Robo Log Styler.
*/
class RoboLogStyle extends LogOutputStyler
{
const TASK_STYLE_SIMULATED = 'options=reverse;bold';
/**
* RoboLogStyle constructor.
*
* @param array $labelStyles
* @param array $messageStyles
*/
public function __construct($labelStyles = [], $messageStyles = [])
{
parent::__construct($labelStyles, $messageStyles);
$this->labelStyles += [
RoboLogLevel::SIMULATED_ACTION => self::TASK_STYLE_SIMULATED,
];
$this->messageStyles += [
RoboLogLevel::SIMULATED_ACTION => '',
];
}
/**
* Log style customization for Robo: replace the log level with
* the task name.
*
* @param string $level
* @param string $message
* @param array $context
*
* @return string
*/
protected function formatMessageByLevel($level, $message, $context)
{
$label = $level;
if (array_key_exists('name', $context)) {
$label = $context['name'];
}
return $this->formatMessage($label, $message, $context, $this->labelStyles[$level], $this->messageStyles[$level]);
}
/**
* Log style customization for Robo: add the time indicator to the
* end of the log message if it exists in the context.
*
* @param string $label
* @param string $message
* @param array $context
* @param string $taskNameStyle
* @param string $messageStyle
*
* @return string
*/
protected function formatMessage($label, $message, $context, $taskNameStyle, $messageStyle = '')
{
$message = parent::formatMessage($label, $message, $context, $taskNameStyle, $messageStyle);
if (array_key_exists('time', $context) && !empty($context['time']) && array_key_exists('timer-label', $context)) {
$duration = TimeKeeper::formatDuration($context['time']);
$message .= ' ' . $context['timer-label'] . ' ' . $this->wrapFormatString($duration, 'fg=yellow');
}
return $message;
}
}
<?php
namespace Robo;
use Robo\Contract\TaskInterface;
use Robo\Contract\LogResultInterface;
use Robo\Exception\TaskExitException;
class Result extends ResultData
{
/**
* @var bool
*/
public static $stopOnFail = false;
/**
* @var \Robo\Contract\TaskInterface
*/
protected $task;
/**
* @param \Robo\Contract\TaskInterface $task
* @param string $exitCode
* @param string $message
* @param array $data
*/
public function __construct(TaskInterface $task, $exitCode, $message = '', $data = [])
{
parent::__construct($exitCode, $message, $data);
$this->task = $task;
$this->printResult();
if (self::$stopOnFail) {
$this->stopOnFail();
}
}
protected function printResult()
{
// For historic reasons, the Result constructor is responsible
// for printing task results.
// TODO: Make IO the responsibility of some other class. Maintaining
// existing behavior for backwards compatibility. This is undesirable
// in the long run, though, as it can result in unwanted repeated input
// in task collections et. al.
$resultPrinter = Robo::resultPrinter();
if ($resultPrinter) {
if ($resultPrinter->printResult($this)) {
$this->data['already-printed'] = true;
}
}
}
/**
* @param \Robo\Contract\TaskInterface $task
* @param string $extension
* @param string $service
*
* @return \Robo\Result
*/
public static function errorMissingExtension(TaskInterface $task, $extension, $service)
{
$messageTpl = 'PHP extension required for %s. Please enable %s';
$message = sprintf($messageTpl, $service, $extension);
return self::error($task, $message);
}
/**
* @param \Robo\Contract\TaskInterface $task
* @param string $class
* @param string $package
*
* @return \Robo\Result
*/
public static function errorMissingPackage(TaskInterface $task, $class, $package)
{
$messageTpl = 'Class %s not found. Please install %s Composer package';
$message = sprintf($messageTpl, $class, $package);
return self::error($task, $message);
}
/**
* @param \Robo\Contract\TaskInterface $task
* @param string $message
* @param array $data
*
* @return \Robo\Result
*/
public static function error(TaskInterface $task, $message, $data = [])
{
return new self($task, self::EXITCODE_ERROR, $message, $data);
}
/**
* @param \Robo\Contract\TaskInterface $task
* @param \Exception $e
* @param array $data
*
* @return \Robo\Result
*/
public static function fromException(TaskInterface $task, \Exception $e, $data = [])
{
$exitCode = $e->getCode();
if (!$exitCode) {
$exitCode = self::EXITCODE_ERROR;
}
return new self($task, $exitCode, $e->getMessage(), $data);
}
/**
* @param \Robo\Contract\TaskInterface $task
* @param string $message
* @param array $data
*
* @return \Robo\Result
*/
public static function success(TaskInterface $task, $message = '', $data = [])
{
return new self($task, self::EXITCODE_OK, $message, $data);
}
/**
* Return a context useful for logging messages.
*
* @return array
*/
public function getContext()
{
$task = $this->getTask();
return TaskInfo::getTaskContext($task) + [
'code' => $this->getExitCode(),
'data' => $this->getArrayCopy(),
'time' => $this->getExecutionTime(),
'message' => $this->getMessage(),
];
}
/**
* Add the results from the most recent task to the accumulated
* results from all tasks that have run so far, merging data
* as necessary.
*
* @param int|string $key
* @param \Robo\Result $taskResult
*/
public function accumulate($key, Result $taskResult)
{
// If the task is unnamed, then all of its data elements
// just get merged in at the top-level of the final Result object.
if (static::isUnnamed($key)) {
$this->merge($taskResult);
} elseif (isset($this[$key])) {
// There can only be one task with a given name; however, if
// there are tasks added 'before' or 'after' the named task,
// then the results from these will be stored under the same
// name unless they are given a name of their own when added.
$current = $this[$key];
$this[$key] = $taskResult->merge($current);
} else {
$this[$key] = $taskResult;
}
}
/**
* We assume that named values (e.g. for associative array keys)
* are non-numeric; numeric keys are presumed to simply be the
* index of an array, and therefore insignificant.
*
* @param int|string $key
*
* @return bool
*/
public static function isUnnamed($key)
{
return is_numeric($key);
}
/**
* @return \Robo\Contract\TaskInterface
*/
public function getTask()
{
return $this->task;
}
/**
* @return \Robo\Contract\TaskInterface
*/
public function cloneTask()
{
$reflect = new \ReflectionClass(get_class($this->task));
return $reflect->newInstanceArgs(func_get_args());
}
/**
* @return bool
*
* @deprecated since 1.0.
*
* @see wasSuccessful()
*/
public function __invoke()
{
trigger_error(__METHOD__ . ' is deprecated: use wasSuccessful() instead.', E_USER_DEPRECATED);
return $this->wasSuccessful();
}
/**
* @return $this
*/
public function stopOnFail()
{
if (!$this->wasSuccessful()) {
$resultPrinter = Robo::resultPrinter();
if ($resultPrinter) {
$resultPrinter->printStopOnFail($this);
}
$this->exitEarly($this->getExitCode());
}
return $this;
}
/**
* @param int $status
*
* @throws \Robo\Exception\TaskExitException
*/
private function exitEarly($status)
{
throw new TaskExitException($this->getTask(), $this->getMessage(), $status);
}
}
<?php
namespace Robo;
use Robo\Contract\LogResultInterface;
use Consolidation\AnnotatedCommand\ExitCodeInterface;
use Consolidation\AnnotatedCommand\OutputDataInterface;
class ResultData extends \ArrayObject implements ExitCodeInterface, OutputDataInterface
{
/**
* @var int
*/
protected $exitCode;
/**
* @var string
*/
protected $message;
const EXITCODE_OK = 0;
const EXITCODE_ERROR = 1;
/** Symfony Console handles these conditions; Robo returns the status
code selected by Symfony. These are here for documentation purposes. */
const EXITCODE_MISSING_OPTIONS = 2;
const EXITCODE_COMMAND_NOT_FOUND = 127;
/** The command was aborted because the user chose to cancel it at some prompt.
This exit code is arbitrarily the same as EX_TEMPFAIL in sysexits.h, although
note that shell error codes are distinct from C exit codes, so this alignment
not particularly meaningful. */
const EXITCODE_USER_CANCEL = 75;
/**
* @param int $exitCode
* @param string $message
* @param array $data
*/
public function __construct($exitCode = self::EXITCODE_OK, $message = '', $data = [])
{
$this->exitCode = $exitCode;
$this->message = $message;
parent::__construct($data);
}
/**
* @param string $message
* @param array $data
*
* @return \Robo\ResultData
*/
public static function message($message, $data = [])
{
return new self(self::EXITCODE_OK, $message, $data);
}
/**
* @param string $message
* @param array $data
*
* @return \Robo\ResultData
*/
public static function cancelled($message = '', $data = [])
{
return new ResultData(self::EXITCODE_USER_CANCEL, $message, $data);
}
/**
* @return array
*/
public function getData()
{
return $this->getArrayCopy();
}
/**
* @return int
*/
public function getExitCode()
{
return $this->exitCode;
}
/**
* @return null|string
*/
public function getOutputData()
{
if (!empty($this->message) && !isset($this['already-printed'])) {
return $this->message;
}
}
/**
* @return string
*/
public function getMessage()
{
return $this->message;
}
/**
* @return bool
*/
public function wasSuccessful()
{
return $this->exitCode === self::EXITCODE_OK;
}
/**
* @return bool
*/
public function wasCancelled()
{
return $this->exitCode == self::EXITCODE_USER_CANCEL;
}
/**
* Merge another result into this result. Data already
* existing in this result takes precedence over the
* data in the Result being merged.
*
* @param \Robo\ResultData $result
*
* @return $this
*/
public function merge(ResultData $result)
{
$mergedData = $this->getArrayCopy() + $result->getArrayCopy();
$this->exchangeArray($mergedData);
return $this;
}
/**
* @return bool
*/
public function hasExecutionTime()
{
return isset($this['time']);
}
/**
* @return null|float
*/
public function getExecutionTime()
{
if (!$this->hasExecutionTime()) {
return null;
}
return $this['time'];
}
}
<?php
namespace Robo;
use League\Container\Container;
use League\Container\ContainerInterface;
use Symfony\Component\Console\Input\StringInput;
use Symfony\Component\Console\Application as SymfonyApplication;
/**
* Manages the container reference and other static data. Favor
* using dependency injection wherever possible. Avoid using
* this class directly, unless setting up a custom DI container.
*/
class Robo
{
const APPLICATION_NAME = 'Robo';
const VERSION = '1.0.4';
/**
* The currently active container object, or NULL if not initialized yet.
*
* @var ContainerInterface|null
*/
protected static $container;
/**
* Entrypoint for standalone Robo-based tools. See docs/framework.md.
*
* @param string[] $argv
* @param string $commandClasses
* @param null|string $appName
* @param null|string $appVersion
* @param null|\Symfony\Component\Console\Output\OutputInterface $output
*
* @return int
*/
public static function run($argv, $commandClasses, $appName = null, $appVersion = null, $output = null)
{
$runner = new \Robo\Runner($commandClasses);
$statusCode = $runner->execute($argv, $appName, $appVersion, $output);
return $statusCode;
}
/**
* Sets a new global container.
*
* @param ContainerInterface $container
* A new container instance to replace the current.
*/
public static function setContainer(ContainerInterface $container)
{
static::$container = $container;
}
/**
* Unsets the global container.
*/
public static function unsetContainer()
{
static::$container = null;
}
/**
* Returns the currently active global container.
*
* @return \League\Container\ContainerInterface
*
* @throws \RuntimeException
*/
public static function getContainer()
{
if (static::$container === null) {
throw new \RuntimeException('container is not initialized yet. \Robo\Robo::setContainer() must be called with a real container.');
}
return static::$container;
}
/**
* Returns TRUE if the container has been initialized, FALSE otherwise.
*
* @return bool
*/
public static function hasContainer()
{
return static::$container !== null;
}
/**
* Create a container and initiailze it. If you wish to *change*
* anything defined in the container, then you should call
* \Robo::configureContainer() instead of this function.
*
* @param null|\Symfony\Component\Console\Input\InputInterface $input
* @param null|\Symfony\Component\Console\Output\OutputInterface $output
* @param null|\Robo\Application $app
* @param null|\Robo\Config $config
*
* @return \League\Container\Container|\League\Container\ContainerInterface
*/
public static function createDefaultContainer($input = null, $output = null, $app = null, $config = null)
{
// Do not allow this function to be called more than once.
if (static::hasContainer()) {
return static::getContainer();
}
if (!$app) {
$app = static::createDefaultApplication();
}
if (!$config) {
$config = new Config();
}
// Set up our dependency injection container.
$container = new Container();
static::configureContainer($container, $app, $config, $input, $output);
// Set the application dispatcher
$app->setDispatcher($container->get('eventDispatcher'));
return $container;
}
/**
* Initialize a container with all of the default Robo services.
* IMPORTANT: after calling this method, clients MUST call:
*
* $dispatcher = $container->get('eventDispatcher');
* $app->setDispatcher($dispatcher);
*
* Any modification to the container should be done prior to fetching
* objects from it.
*
* It is recommended to use \Robo::createDefaultContainer()
* instead, which does all required setup for the caller, but has
* the limitation that the container it creates can only be
* extended, not modified.
*
* @param \League\Container\ContainerInterface $container
* @param \Symfony\Component\Console\Application $app
* @param \Robo\Config $config
* @param null|\Symfony\Component\Console\Input\InputInterface $input
* @param null|\Symfony\Component\Console\Output\OutputInterface $output
*/
public static function configureContainer(ContainerInterface $container, SymfonyApplication $app, Config $config, $input = null, $output = null)
{
// Self-referential container refernce for the inflector
$container->add('container', $container);
static::setContainer($container);
// Create default input and output objects if they were not provided
if (!$input) {
$input = new StringInput('');
}
if (!$output) {
$output = new \Symfony\Component\Console\Output\ConsoleOutput();
}
$config->setDecorated($output->isDecorated());
$container->share('application', $app);
$container->share('config', $config);
$container->share('input', $input);
$container->share('output', $output);
// Register logging and related services.
$container->share('logStyler', \Robo\Log\RoboLogStyle::class);
$container->share('logger', \Robo\Log\RoboLogger::class)
->withArgument('output')
->withMethodCall('setLogOutputStyler', ['logStyler']);
$container->add('progressBar', \Symfony\Component\Console\Helper\ProgressBar::class)
->withArgument('output');
$container->share('progressIndicator', \Robo\Common\ProgressIndicator::class)
->withArgument('progressBar')
->withArgument('output');
$container->share('resultPrinter', \Robo\Log\ResultPrinter::class);
$container->add('simulator', \Robo\Task\Simulator::class);
$container->share('globalOptionsEventListener', \Robo\GlobalOptionsEventListener::class);
$container->share('collectionProcessHook', \Robo\Collection\CollectionProcessHook::class);
$container->share('hookManager', \Consolidation\AnnotatedCommand\Hooks\HookManager::class)
->withMethodCall('addResultProcessor', ['collectionProcessHook', '*']);
$container->share('alterOptionsCommandEvent', \Consolidation\AnnotatedCommand\Options\AlterOptionsCommandEvent::class)
->withArgument('application');
$container->share('eventDispatcher', \Symfony\Component\EventDispatcher\EventDispatcher::class)
->withMethodCall('addSubscriber', ['globalOptionsEventListener'])
->withMethodCall('addSubscriber', ['alterOptionsCommandEvent'])
->withMethodCall('addSubscriber', ['hookManager']);
$container->share('formatterManager', \Consolidation\OutputFormatters\FormatterManager::class)
->withMethodCall('addDefaultFormatters', [])
->withMethodCall('addDefaultSimplifiers', []);
$container->share('commandProcessor', \Consolidation\AnnotatedCommand\CommandProcessor::class)
->withArgument('hookManager')
->withMethodCall('setFormatterManager', ['formatterManager'])
->withMethodCall(
'setDisplayErrorFunction',
[
function ($output, $message) use ($container) {
$logger = $container->get('logger');
$logger->error($message);
}
]
);
$container->share('commandFactory', \Consolidation\AnnotatedCommand\AnnotatedCommandFactory::class)
->withMethodCall('setCommandProcessor', ['commandProcessor']);
$container->add('collection', \Robo\Collection\Collection::class);
$container->add('collectionBuilder', \Robo\Collection\CollectionBuilder::class);
static::addInflectors($container);
// Make sure the application is appropriately initialized.
$app->setAutoExit(false);
}
/**
* @param null|string $appName
* @param null|string $appVersion
*
* @return \Robo\Application
*/
public static function createDefaultApplication($appName = null, $appVersion = null)
{
$appName = $appName ?: self::APPLICATION_NAME;
$appVersion = $appVersion ?: self::VERSION;
$app = new \Robo\Application($appName, $appVersion);
$app->setAutoExit(false);
return $app;
}
/**
* Add the Robo League\Container inflectors to the container
*
* @param \League\Container\ContainerInterface $container
*/
public static function addInflectors($container)
{
// Register our various inflectors.
$container->inflector(\Robo\Contract\ConfigAwareInterface::class)
->invokeMethod('setConfig', ['config']);
$container->inflector(\Psr\Log\LoggerAwareInterface::class)
->invokeMethod('setLogger', ['logger']);
$container->inflector(\League\Container\ContainerAwareInterface::class)
->invokeMethod('setContainer', ['container']);
$container->inflector(\Symfony\Component\Console\Input\InputAwareInterface::class)
->invokeMethod('setInput', ['input']);
$container->inflector(\Robo\Contract\OutputAwareInterface::class)
->invokeMethod('setOutput', ['output']);
$container->inflector(\Robo\Contract\ProgressIndicatorAwareInterface::class)
->invokeMethod('setProgressIndicator', ['progressIndicator']);
}
/**
* Retrieves a service from the container.
*
* Use this method if the desired service is not one of those with a dedicated
* accessor method below. If it is listed below, those methods are preferred
* as they can return useful type hints.
*
* @param string $id
* The ID of the service to retrieve.
*
* @return mixed
* The specified service.
*/
public static function service($id)
{
return static::getContainer()->get($id);
}
/**
* Indicates if a service is defined in the container.
*
* @param string $id
* The ID of the service to check.
*
* @return bool
* TRUE if the specified service exists, FALSE otherwise.
*/
public static function hasService($id)
{
// Check hasContainer() first in order to always return a Boolean.
return static::hasContainer() && static::getContainer()->has($id);
}
/**
* Return the result printer object.
*
* @return \Robo\Log\ResultPrinter
*/
public static function resultPrinter()
{
return static::service('resultPrinter');
}
/**
* @return \Robo\Config
*/
public static function config()
{
return static::service('config');
}
/**
* @return \Consolidation\Log\Logger
*/
public static function logger()
{
return static::service('logger');
}
/**
* @return \Robo\Application
*/
public static function application()
{
return static::service('application');
}
/**
* Return the output object.
*
* @return \Symfony\Component\Console\Output\OutputInterface
*/
public static function output()
{
return static::service('output');
}
/**
* Return the input object.
*
* @return \Symfony\Component\Console\Input\InputInterface
*/
public static function input()
{
return static::service('input');
}
}
<?php
namespace Robo;
use League\Container\Container;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Input\StringInput;
use Consolidation\AnnotatedCommand\PassThroughArgsInput;
use Robo\Contract\BuilderAwareInterface;
use Robo\Common\IO;
use Robo\Exception\TaskExitException;
use League\Container\ContainerInterface;
use League\Container\ContainerAwareInterface;
use League\Container\ContainerAwareTrait;
class Runner implements ContainerAwareInterface
{
const ROBOCLASS = 'RoboFile';
const ROBOFILE = 'RoboFile.php';
use IO;
use ContainerAwareTrait;
/**
* @var string
*/
protected $roboClass;
/**
* @var string
*/
protected $roboFile;
/**
* @var string working dir of Robo
*/
protected $dir;
/**
* Class Constructor
*
* @param null|string $roboClass
* @param null|string $roboFile
*/
public function __construct($roboClass = null, $roboFile = null)
{
// set the const as class properties to allow overwriting in child classes
$this->roboClass = $roboClass ? $roboClass : self::ROBOCLASS ;
$this->roboFile = $roboFile ? $roboFile : self::ROBOFILE;
$this->dir = getcwd();
}
/**
* @param \Symfony\Component\Console\Output\OutputInterface $output
*
* @return bool
*/
protected function loadRoboFile($output)
{
// If we have not been provided an output object, make a temporary one.
if (!$output) {
$output = new \Symfony\Component\Console\Output\ConsoleOutput();
}
// If $this->roboClass is a single class that has not already
// been loaded, then we will try to obtain it from $this->roboFile.
// If $this->roboClass is an array, we presume all classes requested
// are available via the autoloader.
if (is_array($this->roboClass) || class_exists($this->roboClass)) {
return true;
}
if (!file_exists($this->dir)) {
$output->writeln("<error>Path `{$this->dir}` is invalid; please provide a valid absolute path to the Robofile to load.</error>");
return false;
}
$realDir = realpath($this->dir);
$roboFilePath = $realDir . DIRECTORY_SEPARATOR . $this->roboFile;
if (!file_exists($roboFilePath)) {
$requestedRoboFilePath = $this->dir . DIRECTORY_SEPARATOR . $this->roboFile;
$output->writeln("<error>Requested RoboFile `$requestedRoboFilePath` is invalid, please provide valid absolute path to load Robofile</error>");
return false;
}
require_once $roboFilePath;
if (!class_exists($this->roboClass)) {
$output->writeln("<error>Class ".$this->roboClass." was not loaded</error>");
return false;
}
return true;
}
/**
* @param array $argv
* @param null|string $appName
* @param null|string $appVersion
* @param null|\Symfony\Component\Console\Output\OutputInterface $output
*
* @return int
*/
public function execute($argv, $appName = null, $appVersion = null, $output = null)
{
$argv = $this->shebang($argv);
$argv = $this->processRoboOptions($argv);
$app = null;
if ($appName && $appVersion) {
$app = Robo::createDefaultApplication($appName, $appVersion);
}
$commandFiles = $this->getRoboFileCommands($output);
return $this->run($argv, $output, $app, $commandFiles);
}
/**
* @param null|\Symfony\Component\Console\Input\InputInterface $input
* @param null|\Symfony\Component\Console\Output\OutputInterface $output
* @param null|\Robo\Application $app
* @param array[] $commandFiles
*
* @return int
*/
public function run($input = null, $output = null, $app = null, $commandFiles = [])
{
// Create default input and output objects if they were not provided
if (!$input) {
$input = new StringInput('');
}
if (is_array($input)) {
$input = new ArgvInput($input);
}
if (!$output) {
$output = new \Symfony\Component\Console\Output\ConsoleOutput();
}
$this->setInput($input);
$this->setOutput($output);
// If we were not provided a container, then create one
if (!$this->getContainer()) {
$container = Robo::createDefaultContainer($input, $output, $app);
$this->setContainer($container);
// Automatically register a shutdown function and
// an error handler when we provide the container.
$this->installRoboHandlers();
}
if (!$app) {
$app = Robo::application();
}
if (!isset($commandFiles)) {
$this->yell("Robo is not initialized here. Please run `robo init` to create a new RoboFile", 40, 'yellow');
$app->addInitRoboFileCommand($this->roboFile, $this->roboClass);
$commandFiles = [];
}
$this->registerCommandClasses($app, $commandFiles);
try {
$statusCode = $app->run($input, $output);
} catch (TaskExitException $e) {
$statusCode = $e->getCode() ?: 1;
}
return $statusCode;
}
/**
* @param \Symfony\Component\Console\Output\OutputInterface $output
*
* @return null|string
*/
protected function getRoboFileCommands($output)
{
if (!$this->loadRoboFile($output)) {
return;
}
return $this->roboClass;
}
/**
* @param \Robo\Application $app
* @param array $commandClasses
*/
public function registerCommandClasses($app, $commandClasses)
{
foreach ((array)$commandClasses as $commandClass) {
$this->registerCommandClass($app, $commandClass);
}
}
/**
* @param \Robo\Application $app
* @param string|BuilderAwareInterface|ContainerAwareInterface $commandClass
*
* @return mixed|void
*/
public function registerCommandClass($app, $commandClass)
{
$container = Robo::getContainer();
$roboCommandFileInstance = $this->instantiateCommandClass($commandClass);
if (!$roboCommandFileInstance) {
return;
}
// Register commands for all of the public methods in the RoboFile.
$commandFactory = $container->get('commandFactory');
$commandList = $commandFactory->createCommandsFromClass($roboCommandFileInstance);
foreach ($commandList as $command) {
$app->add($command);
}
return $roboCommandFileInstance;
}
/**
* @param string|BuilderAwareInterface|ContainerAwareInterface $commandClass
*
* @return null|object
*/
protected function instantiateCommandClass($commandClass)
{
$container = Robo::getContainer();
// Register the RoboFile with the container and then immediately
// fetch it; this ensures that all of the inflectors will run.
// If the command class is already an instantiated object, then
// just use it exactly as it was provided to us.
if (is_string($commandClass)) {
$reflectionClass = new \ReflectionClass($commandClass);
if ($reflectionClass->isAbstract()) {
return;
}
$commandFileName = "{$commandClass}Commands";
$container->share($commandFileName, $commandClass);
$commandClass = $container->get($commandFileName);
}
// If the command class is a Builder Aware Interface, then
// ensure that it has a builder. Every command class needs
// its own collection builder, as they have references to each other.
if ($commandClass instanceof BuilderAwareInterface) {
$builder = $container->get('collectionBuilder', [$commandClass]);
$commandClass->setBuilder($builder);
}
if ($commandClass instanceof ContainerAwareInterface) {
$commandClass->setContainer($container);
}
return $commandClass;
}
public function installRoboHandlers()
{
register_shutdown_function(array($this, 'shutdown'));
set_error_handler(array($this, 'handleError'));
}
/**
* Process a shebang script, if one was used to launch this Runner.
*
* @param array $args
*
* @return array $args with shebang script removed
*/
protected function shebang($args)
{
// Option 1: Shebang line names Robo, but includes no parameters.
// #!/bin/env robo
// The robo class may contain multiple commands; the user may
// select which one to run, or even get a list of commands or
// run 'help' on any of the available commands as usual.
if ((count($args) > 1) && $this->isShebangFile($args[1])) {
return array_merge([$args[0]], array_slice($args, 2));
}
// Option 2: Shebang line stipulates which command to run.
// #!/bin/env robo mycommand
// The robo class must contain a public method named 'mycommand'.
// This command will be executed every time. Arguments and options
// may be provided on the commandline as usual.
if ((count($args) > 2) && $this->isShebangFile($args[2])) {
return array_merge([$args[0]], explode(' ', $args[1]), array_slice($args, 3));
}
return $args;
}
/**
* Determine if the specified argument is a path to a shebang script.
* If so, load it.
*
* @param string $filepath file to check
*
* @return bool Returns TRUE if shebang script was processed
*/
protected function isShebangFile($filepath)
{
if (!is_file($filepath)) {
return false;
}
$fp = fopen($filepath, "r");
if ($fp === false) {
return false;
}
$line = fgets($fp);
$result = $this->isShebangLine($line);
if ($result) {
while ($line = fgets($fp)) {
$line = trim($line);
if ($line == '<?php') {
$script = stream_get_contents($fp);
if (preg_match('#^class *([^ ]+)#m', $script, $matches)) {
$this->roboClass = $matches[1];
eval($script);
$result = true;
}
}
}
}
fclose($fp);
return $result;
}
/**
* Test to see if the provided line is a robo 'shebang' line.
*
* @param string $line
*
* @return bool
*/
protected function isShebangLine($line)
{
return ((substr($line, 0, 2) == '#!') && (strstr($line, 'robo') !== false));
}
/**
* Check for Robo-specific arguments such as --load-from, process them,
* and remove them from the array. We have to process --load-from before
* we set up Symfony Console.
*
* @param array $argv
*
* @return array
*/
protected function processRoboOptions($argv)
{
// loading from other directory
$pos = $this->arraySearchBeginsWith('--load-from', $argv) ?: array_search('-f', $argv);
if ($pos === false) {
return $argv;
}
$passThru = array_search('--', $argv);
if (($passThru !== false) && ($passThru < $pos)) {
return $argv;
}
if (substr($argv[$pos], 0, 12) == '--load-from=') {
$this->dir = substr($argv[$pos], 12);
} elseif (isset($argv[$pos +1])) {
$this->dir = $argv[$pos +1];
unset($argv[$pos +1]);
}
unset($argv[$pos]);
// Make adjustments if '--load-from' points at a file.
if (is_file($this->dir) || (substr($this->dir, -4) == '.php')) {
$this->roboFile = basename($this->dir);
$this->dir = dirname($this->dir);
$className = basename($this->roboFile, '.php');
if ($className != $this->roboFile) {
$this->roboClass = $className;
}
}
// Convert directory to a real path, but only if the
// path exists. We do not want to lose the original
// directory if the user supplied a bad value.
$realDir = realpath($this->dir);
if ($realDir) {
chdir($realDir);
$this->dir = $realDir;
}
return $argv;
}
/**
* @param string $needle
* @param string[] $haystack
*
* @return bool|int
*/
protected function arraySearchBeginsWith($needle, $haystack)
{
for ($i = 0; $i < count($haystack); ++$i) {
if (substr($haystack[$i], 0, strlen($needle)) == $needle) {
return $i;
}
}
return false;
}
public function shutdown()
{
$error = error_get_last();
if (!is_array($error)) {
return;
}
$this->writeln(sprintf("<error>ERROR: %s \nin %s:%d\n</error>", $error['message'], $error['file'], $error['line']));
}
/**
* This is just a proxy error handler that checks the current error_reporting level.
* In case error_reporting is disabled the error is marked as handled, otherwise
* the normal internal error handling resumes.
*
* @return bool
*/
public function handleError()
{
if (error_reporting() === 0) {
return true;
}
return false;
}
}
<?php
namespace Robo\Task\ApiGen;
use Robo\Contract\CommandInterface;
use Robo\Exception\TaskException;
use Robo\Task\BaseTask;
use Traversable;
/**
* Executes ApiGen command to generate documentation
*
* ``` php
* <?php
* // ApiGen Command
* $this->taskApiGen('./apigen.neon')
* ->templateConfig('vendor/apigen/apigen/templates/bootstrap/config.neon')
* ->wipeout(true)
* ->run();
* ?>
* ```
*/
class ApiGen extends BaseTask implements CommandInterface
{
use \Robo\Common\ExecOneCommand;
const BOOL_NO = 'no';
const BOOL_YES = 'yes';
/**
* @var string
*/
protected $command;
/**
* @param null|string $pathToApiGen
*
* @throws \Robo\Exception\TaskException
*/
public function __construct($pathToApiGen = null)
{
$this->command = $pathToApiGen;
if (!$this->command) {
$this->command = $this->findExecutablePhar('apigen');
}
if (!$this->command) {
throw new TaskException(__CLASS__, "No apigen installation found");
}
}
/**
* @param array|Traversable|string $arg a single object or something traversable
*
* @return array|Traversable the provided argument if it was already traversable, or the given
* argument returned as a one-element array
*/
protected static function forceTraversable($arg)
{
$traversable = $arg;
if (!is_array($traversable) && !($traversable instanceof \Traversable)) {
$traversable = array($traversable);
}
return $traversable;
}
/**
* @param array|string $arg a single argument or an array of multiple string values
*
* @return string a comma-separated string of all of the provided arguments, suitable
* as a command-line "list" type argument for ApiGen
*/
protected static function asList($arg)
{
$normalized = is_array($arg) ? $arg : array($arg);
return implode(',', $normalized);
}
/**
* @param bool|string $val an argument to be normalized
* @param string $default one of self::BOOL_YES or self::BOOK_NO if the provided
* value could not deterministically be converted to a
* yes or no value
*
* @return string the given value as a command-line "yes|no" type of argument for ApiGen,
* or the default value if none could be determined
*/
protected static function asTextBool($val, $default)
{
if ($val === self::BOOL_YES || $val === self::BOOL_NO) {
return $val;
}
if (!$val) {
return self::BOOL_NO;
}
if ($val === true) {
return self::BOOL_YES;
}
if (is_numeric($val) && $val != 0) {
return self::BOOL_YES;
}
if (strcasecmp($val[0], 'y') === 0) {
return self::BOOL_YES;
}
if (strcasecmp($val[0], 'n') === 0) {
return self::BOOL_NO;
}
// meh, good enough, let apigen sort it out
return $default;
}
/**
* @param string $config
*
* @return $this
*/
public function config($config)
{
$this->option('config', $config);
return $this;
}
/**
* @param array|string|Traversable $src one or more source values
*
* @return $this
*/
public function source($src)
{
foreach (self::forceTraversable($src) as $source) {
$this->option('source', $source);
}
return $this;
}
/**
* @param string $dest
*
* @return $this
*/
public function destination($dest)
{
$this->option('destination', $dest);
return $this;
}
/**
* @param array|string $exts one or more extensions
*
* @return $this
*/
public function extensions($exts)
{
$this->option('extensions', self::asList($exts));
return $this;
}
/**
* @param array|string $exclude one or more exclusions
*
* @return $this
*/
public function exclude($exclude)
{
foreach (self::forceTraversable($exclude) as $excl) {
$this->option('exclude', $excl);
}
return $this;
}
/**
* @param array|string|Traversable $path one or more skip-doc-path values
*
* @return $this
*/
public function skipDocPath($path)
{
foreach (self::forceTraversable($path) as $skip) {
$this->option('skip-doc-path', $skip);
}
return $this;
}
/**
* @param array|string|Traversable $prefix one or more skip-doc-prefix values
*
* @return $this
*/
public function skipDocPrefix($prefix)
{
foreach (self::forceTraversable($prefix) as $skip) {
$this->option('skip-doc-prefix', $skip);
}
return $this;
}
/**
* @param array|string $charset one or more charsets
*
* @return $this
*/
public function charset($charset)
{
$this->option('charset', self::asList($charset));
return $this;
}
/**
* @param string $name
*
* @return $this
*/
public function mainProjectNamePrefix($name)
{
$this->option('main', $name);
return $this;
}
/**
* @param string $title
*
* @return $this
*/
public function title($title)
{
$this->option('title', $title);
return $this;
}
/**
* @param string $baseUrl
*
* @return $this
*/
public function baseUrl($baseUrl)
{
$this->option('base-url', $baseUrl);
return $this;
}
/**
* @param string $id
*
* @return $this
*/
public function googleCseId($id)
{
$this->option('google-cse-id', $id);
return $this;
}
/**
* @param string $trackingCode
*
* @return $this
*/
public function googleAnalytics($trackingCode)
{
$this->option('google-analytics', $trackingCode);
return $this;
}
/**
* @param mixed $templateConfig
*
* @return $this
*/
public function templateConfig($templateConfig)
{
$this->option('template-config', $templateConfig);
return $this;
}
/**
* @param array|string $tags one or more supported html tags
*
* @return $this
*/
public function allowedHtml($tags)
{
$this->option('allowed-html', self::asList($tags));
return $this;
}
/**
* @param string $groups
*
* @return $this
*/
public function groups($groups)
{
$this->option('groups', $groups);
return $this;
}
/**
* @param array|string $types or more supported autocomplete types
*
* @return $this
*/
public function autocomplete($types)
{
$this->option('autocomplete', self::asList($types));
return $this;
}
/**
* @param array|string $levels one or more access levels
*
* @return $this
*/
public function accessLevels($levels)
{
$this->option('access-levels', self::asList($levels));
return $this;
}
/**
* @param boolean|string $internal 'yes' or true if internal, 'no' or false if not
*
* @return $this
*/
public function internal($internal)
{
$this->option('internal', self::asTextBool($internal, self::BOOL_NO));
return $this;
}
/**
* @param boolean|string $php 'yes' or true to generate documentation for internal php classes,
* 'no' or false otherwise
*
* @return $this
*/
public function php($php)
{
$this->option('php', self::asTextBool($php, self::BOOL_YES));
return $this;
}
/**
* @param bool|string $tree 'yes' or true to generate a tree view of classes, 'no' or false otherwise
*
* @return $this
*/
public function tree($tree)
{
$this->option('tree', self::asTextBool($tree, self::BOOL_YES));
return $this;
}
/**
* @param bool|string $dep 'yes' or true to generate documentation for deprecated classes, 'no' or false otherwise
*
* @return $this
*/
public function deprecated($dep)
{
$this->option('deprecated', self::asTextBool($dep, self::BOOL_NO));
return $this;
}
/**
* @param bool|string $todo 'yes' or true to document tasks, 'no' or false otherwise
*
* @return $this
*/
public function todo($todo)
{
$this->option('todo', self::asTextBool($todo, self::BOOL_NO));
return $this;
}
/**
* @param bool|string $src 'yes' or true to generate highlighted source code, 'no' or false otherwise
*
* @return $this
*/
public function sourceCode($src)
{
$this->option('source-code', self::asTextBool($src, self::BOOL_YES));
return $this;
}
/**
* @param bool|string $zipped 'yes' or true to generate downloadable documentation, 'no' or false otherwise
*
* @return $this
*/
public function download($zipped)
{
$this->option('download', self::asTextBool($zipped, self::BOOL_NO));
return $this;
}
public function report($path)
{
$this->option('report', $path);
return $this;
}
/**
* @param bool|string $wipeout 'yes' or true to clear out the destination directory, 'no' or false otherwise
*
* @return $this
*/
public function wipeout($wipeout)
{
$this->option('wipeout', self::asTextBool($wipeout, self::BOOL_YES));
return $this;
}
/**
* @param bool|string $quiet 'yes' or true for quiet, 'no' or false otherwise
*
* @return $this
*/
public function quiet($quiet)
{
$this->option('quiet', self::asTextBool($quiet, self::BOOL_NO));
return $this;
}
/**
* @param bool|string $bar 'yes' or true to display a progress bar, 'no' or false otherwise
*
* @return $this
*/
public function progressbar($bar)
{
$this->option('progressbar', self::asTextBool($bar, self::BOOL_YES));
return $this;
}
/**
* @param bool|string $colors 'yes' or true colorize the output, 'no' or false otherwise
*
* @return $this
*/
public function colors($colors)
{
$this->option('colors', self::asTextBool($colors, self::BOOL_YES));
return $this;
}
/**
* @param bool|string $check 'yes' or true to check for updates, 'no' or false otherwise
*
* @return $this
*/
public function updateCheck($check)
{
$this->option('update-check', self::asTextBool($check, self::BOOL_YES));
return $this;
}
/**
* @param bool|string $debug 'yes' or true to enable debug mode, 'no' or false otherwise
*
* @return $this
*/
public function debug($debug)
{
$this->option('debug', self::asTextBool($debug, self::BOOL_NO));
return $this;
}
/**
* {@inheritdoc}
*/
public function getCommand()
{
return $this->command . $this->arguments;
}
/**
* {@inheritdoc}
*/
public function run()
{
$this->printTaskInfo('Running ApiGen {args}', ['args' => $this->arguments]);
return $this->executeCommand($this->getCommand());
}
}
<?php
namespace Robo\Task\ApiGen;
trait loadTasks
{
/**
* @param null|string $pathToApiGen
*
* @return \Robo\Task\ApiGen\ApiGen
*/
protected function taskApiGen($pathToApiGen = null)
{
return $this->task(ApiGen::class, $pathToApiGen);
}
}
<?php
namespace Robo\Task\Archive;
use Robo\Result;
use Robo\Task\BaseTask;
use Robo\Task\Filesystem\FilesystemStack;
use Robo\Task\Filesystem\DeleteDir;
use Robo\Contract\BuilderAwareInterface;
use Robo\Common\BuilderAwareTrait;
/**
* Extracts an archive.
*
* Note that often, distributions are packaged in tar or zip archives
* where the topmost folder may contain variable information, such as
* the release date, or the version of the package. This information
* is very useful when unpacking by hand, but arbitrarily-named directories
* are much less useful to scripts. Therefore, by default, Extract will
* remove the top-level directory, and instead store all extracted files
* into the directory specified by $archivePath.
*
* To keep the top-level directory when extracting, use
* `preserveTopDirectory(true)`.
*
* ``` php
* <?php
* $this->taskExtract($archivePath)
* ->to($destination)
* ->preserveTopDirectory(false) // the default
* ->run();
* ?>
* ```
*
* @method to(string) location to store extracted files
*/
class Extract extends BaseTask implements BuilderAwareInterface
{
use BuilderAwareTrait;
/**
* @var string
*/
protected $filename;
/**
* @var string
*/
protected $to;
/**
* @var bool
*/
private $preserveTopDirectory = false;
/**
* @param string $filename
*/
public function __construct($filename)
{
$this->filename = $filename;
}
/**
* Location to store extracted files.
*
* @param string $to
*
* @return $this
*/
public function to($to)
{
$this->to = $to;
return $this;
}
/**
* @param bool $preserve
*
* @return $this
*/
public function preserveTopDirectory($preserve = true)
{
$this->preserveTopDirectory = $preserve;
return $this;
}
/**
* {@inheritdoc}
*/
public function run()
{
if (!file_exists($this->filename)) {
$this->printTaskError("File {filename} does not exist", ['filename' => $this->filename]);
return false;
}
if (!($mimetype = static::archiveType($this->filename))) {
$this->printTaskError("Could not determine type of archive for {filename}", ['filename' => $this->filename]);
return false;
}
// We will first extract to $extractLocation and then move to $this->to
$extractLocation = static::getTmpDir();
@mkdir($extractLocation);
@mkdir(dirname($this->to));
$this->startTimer();
$this->printTaskInfo("Extracting {filename}", ['filename' => $this->filename]);
$result = $this->extractAppropriateType($mimetype, $extractLocation);
if ($result->wasSuccessful()) {
$this->printTaskInfo("{filename} extracted", ['filename' => $this->filename]);
// Now, we want to move the extracted files to $this->to. There
// are two possibilities that we must consider:
//
// (1) Archived files were encapsulated in a folder with an arbitrary name
// (2) There was no encapsulating folder, and all the files in the archive
// were extracted into $extractLocation
//
// In the case of (1), we want to move and rename the encapsulating folder
// to $this->to.
//
// In the case of (2), we will just move and rename $extractLocation.
$filesInExtractLocation = glob("$extractLocation/*");
$hasEncapsulatingFolder = ((count($filesInExtractLocation) == 1) && is_dir($filesInExtractLocation[0]));
if ($hasEncapsulatingFolder && !$this->preserveTopDirectory) {
$result = (new FilesystemStack())
->inflect($this)
->rename($filesInExtractLocation[0], $this->to)
->run();
(new DeleteDir($extractLocation))
->inflect($this)
->run();
} else {
$result = (new FilesystemStack())
->inflect($this)
->rename($extractLocation, $this->to)
->run();
}
}
$this->stopTimer();
$result['time'] = $this->getExecutionTime();
return $result;
}
/**
* @param string $mimetype
* @param string $extractLocation
*
* @return \Robo\Result
*/
protected function extractAppropriateType($mimetype, $extractLocation)
{
// Perform the extraction of a zip file.
if (($mimetype == 'application/zip') || ($mimetype == 'application/x-zip')) {
return $this->extractZip($extractLocation);
}
return $this->extractTar($extractLocation);
}
/**
* @param string $extractLocation
*
* @return \Robo\Result
*/
protected function extractZip($extractLocation)
{
if (!extension_loaded('zlib')) {
return Result::errorMissingExtension($this, 'zlib', 'zip extracting');
}
$zip = new \ZipArchive();
if (($status = $zip->open($this->filename)) !== true) {
return Result::error($this, "Could not open zip archive {$this->filename}");
}
if (!$zip->extractTo($extractLocation)) {
return Result::error($this, "Could not extract zip archive {$this->filename}");
}
$zip->close();
return Result::success($this);
}
/**
* @param string $extractLocation
*
* @return \Robo\Result
*/
protected function extractTar($extractLocation)
{
if (!class_exists('Archive_Tar')) {
return Result::errorMissingPackage($this, 'Archive_Tar', 'pear/archive_tar');
}
$tar_object = new \Archive_Tar($this->filename);
if (!$tar_object->extract($extractLocation)) {
return Result::error($this, "Could not extract tar archive {$this->filename}");
}
return Result::success($this);
}
/**
* @param string $filename
*
* @return bool|string
*/
protected static function archiveType($filename)
{
$content_type = false;
if (class_exists('finfo')) {
$finfo = new \finfo(FILEINFO_MIME_TYPE);
$content_type = $finfo->file($filename);
// If finfo cannot determine the content type, then we will try other methods
if ($content_type == 'application/octet-stream') {
$content_type = false;
}
}
// Examing the file's magic header bytes.
if (!$content_type) {
if ($file = fopen($filename, 'rb')) {
$first = fread($file, 2);
fclose($file);
if ($first !== false) {
// Interpret the two bytes as a little endian 16-bit unsigned int.
$data = unpack('v', $first);
switch ($data[1]) {
case 0x8b1f:
// First two bytes of gzip files are 0x1f, 0x8b (little-endian).
// See http://www.gzip.org/zlib/rfc-gzip.html#header-trailer
$content_type = 'application/x-gzip';
break;
case 0x4b50:
// First two bytes of zip files are 0x50, 0x4b ('PK') (little-endian).
// See http://en.wikipedia.org/wiki/Zip_(file_format)#File_headers
$content_type = 'application/zip';
break;
case 0x5a42:
// First two bytes of bzip2 files are 0x5a, 0x42 ('BZ') (big-endian).
// See http://en.wikipedia.org/wiki/Bzip2#File_format
$content_type = 'application/x-bzip2';
break;
}
}
}
}
// 3. Lastly if above methods didn't work, try to guess the mime type from
// the file extension. This is useful if the file has no identificable magic
// header bytes (for example tarballs).
if (!$content_type) {
// Remove querystring from the filename, if present.
$filename = basename(current(explode('?', $filename, 2)));
$extension_mimetype = array(
'.tar.gz' => 'application/x-gzip',
'.tgz' => 'application/x-gzip',
'.tar' => 'application/x-tar',
);
foreach ($extension_mimetype as $extension => $ct) {
if (substr($filename, -strlen($extension)) === $extension) {
$content_type = $ct;
break;
}
}
}
return $content_type;
}
/**
* @return string
*/
protected static function getTmpDir()
{
return getcwd().'/tmp'.rand().time();
}
}
<?php
namespace Robo\Task\Archive;
trait loadTasks
{
/**
* @param $filename
*
* @return Pack
*/
protected function taskPack($filename)
{
return $this->task(Pack::class, $filename);
}
/**
* @param $filename
*
* @return Extract
*/
protected function taskExtract($filename)
{
return $this->task(Extract::class, $filename);
}
}
<?php
namespace Robo\Task\Archive;
use Robo\Contract\PrintedInterface;
use Robo\Result;
use Robo\Task\BaseTask;
use Symfony\Component\Finder\Finder;
/**
* Creates a zip or tar archive.
*
* ``` php
* <?php
* $this->taskPack(
* <archiveFile>)
* ->add('README') // Puts file 'README' in archive at the root
* ->add('project') // Puts entire contents of directory 'project' in archinve inside 'project'
* ->addFile('dir/file.txt', 'file.txt') // Takes 'file.txt' from cwd and puts it in archive inside 'dir'.
* ->run();
* ?>
* ```
*/
class Pack extends BaseTask implements PrintedInterface
{
/**
* The list of items to be packed into the archive.
*
* @var array
*/
private $items = [];
/**
* The full path to the archive to be created.
*
* @var string
*/
private $archiveFile;
/**
* Construct the class.
*
* @param string $archiveFile The full path and name of the archive file to create.
*
* @since 1.0
*/
public function __construct($archiveFile)
{
$this->archiveFile = $archiveFile;
}
/**
* Satisfy the parent requirement.
*
* @return bool Always returns true.
*
* @since 1.0
*/
public function getPrinted()
{
return true;
}
/**
* @param string $archiveFile
*
* @return $this
*/
public function archiveFile($archiveFile)
{
$this->archiveFile = $archiveFile;
return $this;
}
/**
* Add an item to the archive. Like file_exists(), the parameter
* may be a file or a directory.
*
* @var string
* Relative path and name of item to store in archive
* @var string
* Absolute or relative path to file or directory's location in filesystem
*
* @return $this
*/
public function addFile($placementLocation, $filesystemLocation)
{
$this->items[$placementLocation] = $filesystemLocation;
return $this;
}
/**
* Alias for addFile, in case anyone has angst about using
* addFile with a directory.
*
* @var string
* Relative path and name of directory to store in archive
* @var string
* Absolute or relative path to directory or directory's location in filesystem
*
* @return $this
*/
public function addDir($placementLocation, $filesystemLocation)
{
$this->addFile($placementLocation, $filesystemLocation);
return $this;
}
/**
* Add a file or directory, or list of same to the archive.
*
* @var string|array
* If given a string, should contain the relative filesystem path to the
* the item to store in archive; this will also be used as the item's
* path in the archive, so absolute paths should not be used here.
* If given an array, the key of each item should be the path to store
* in the archive, and the value should be the filesystem path to the
* item to store.
* @return $this
*/
public function add($item)
{
if (is_array($item)) {
$this->items = array_merge($this->items, $item);
} else {
$this->addFile($item, $item);
}
return $this;
}
/**
* Create a zip archive for distribution.
*
* @return \Robo\Result
*
* @since 1.0
*/
public function run()
{
$this->startTimer();
// Use the file extension to determine what kind of archive to create.
$fileInfo = new \SplFileInfo($this->archiveFile);
$extension = strtolower($fileInfo->getExtension());
if (empty($extension)) {
return Result::error($this, "Archive filename must use an extension (e.g. '.zip') to specify the kind of archive to create.");
}
try {
// Inform the user which archive we are creating
$this->printTaskInfo("Creating archive {filename}", ['filename' => $this->archiveFile]);
if ($extension == 'zip') {
$result = $this->archiveZip($this->archiveFile, $this->items);
} else {
$result = $this->archiveTar($this->archiveFile, $this->items);
}
$this->printTaskSuccess("{filename} created.", ['filename' => $this->archiveFile]);
} catch (\Exception $e) {
$this->printTaskError("Could not create {filename}. {exception}", ['filename' => $this->archiveFile, 'exception' => $e->getMessage(), '_style' => ['exception' => '']]);
$result = Result::error($this, sprintf('Could not create %s. %s', $this->archiveFile, $e->getMessage()));
}
$this->stopTimer();
$result['time'] = $this->getExecutionTime();
return $result;
}
/**
* @param string $archiveFile
* @param array $items
*
* @return \Robo\Result
*/
protected function archiveTar($archiveFile, $items)
{
if (!class_exists('Archive_Tar')) {
return Result::errorMissingPackage($this, 'Archive_Tar', 'pear/archive_tar');
}
$tar_object = new \Archive_Tar($archiveFile);
foreach ($items as $placementLocation => $filesystemLocation) {
$p_remove_dir = $filesystemLocation;
$p_add_dir = $placementLocation;
if (is_file($filesystemLocation)) {
$p_remove_dir = dirname($filesystemLocation);
$p_add_dir = dirname($placementLocation);
if (basename($filesystemLocation) != basename($placementLocation)) {
return Result::error($this, "Tar archiver does not support renaming files during extraction; could not add $filesystemLocation as $placementLocation.");
}
}
if (!$tar_object->addModify([$filesystemLocation], $p_add_dir, $p_remove_dir)) {
return Result::error($this, "Could not add $filesystemLocation to the archive.");
}
}
return Result::success($this);
}
/**
* @param string $archiveFile
* @param array $items
*
* @return \Robo\Result
*/
protected function archiveZip($archiveFile, $items)
{
if (!extension_loaded('zlib')) {
return Result::errorMissingExtension($this, 'zlib', 'zip packing');
}
$zip = new \ZipArchive($archiveFile, \ZipArchive::CREATE);
if (!$zip->open($archiveFile, \ZipArchive::CREATE)) {
return Result::error($this, "Could not create zip archive {$archiveFile}");
}
$result = $this->addItemsToZip($zip, $items);
$zip->close();
return $result;
}
/**
* @param \ZipArchive $zip
* @param array $items
*
* @return \Robo\Result
*/
protected function addItemsToZip($zip, $items)
{
foreach ($items as $placementLocation => $filesystemLocation) {
if (is_dir($filesystemLocation)) {
$finder = new Finder();
$finder->files()->in($filesystemLocation)->ignoreDotFiles(false);
foreach ($finder as $file) {
// Replace Windows slashes or resulting zip will have issues on *nixes.
$relativePathname = str_replace('\\', '/', $file->getRelativePathname());
if (!$zip->addFile($file->getRealpath(), "{$placementLocation}/{$relativePathname}")) {
return Result::error($this, "Could not add directory $filesystemLocation to the archive; error adding {$file->getRealpath()}.");
}
}
} elseif (is_file($filesystemLocation)) {
if (!$zip->addFile($filesystemLocation, $placementLocation)) {
return Result::error($this, "Could not add file $filesystemLocation to the archive.");
}
} else {
return Result::error($this, "Could not find $filesystemLocation for the archive.");
}
}
return Result::success($this);
}
}
<?php
namespace Robo\Task\Assets;
use Robo\Result;
use Robo\Task\BaseTask;
abstract class CssPreprocessor extends BaseTask
{
const FORMAT_NAME = '';
/**
* Default compiler to use.
*
* @var string
*/
protected $compiler;
/**
* Available compilers list
*
* @var string[]
*/
protected $compilers = [];
/**
* Compiler options.
*
* @var array
*/
protected $compilerOptions = [];
/**
* @var array
*/
protected $files = [];
/**
* Constructor. Accepts array of file paths.
*
* @param array $input
*/
public function __construct(array $input)
{
$this->files = $input;
$this->setDefaultCompiler();
}
protected function setDefaultCompiler()
{
if (isset($this->compilers[0])) {
//set first compiler as default
$this->compiler = $this->compilers[0];
}
}
/**
* Sets import directories
* Alias for setImportPaths
* @see CssPreprocessor::setImportPaths
*
* @param array|string $dirs
*
* @return $this
*/
public function importDir($dirs)
{
return $this->setImportPaths($dirs);
}
/**
* Adds import directory
*
* @param string $dir
*
* @return $this
*/
public function addImportPath($dir)
{
if (!isset($this->compilerOptions['importDirs'])) {
$this->compilerOptions['importDirs'] = [];
}
if (!in_array($dir, $this->compilerOptions['importDirs'], true)) {
$this->compilerOptions['importDirs'][] = $dir;
}
return $this;
}
/**
* Sets import directories
*
* @param array|string $dirs
*
* @return $this
*/
public function setImportPaths($dirs)
{
if (!is_array($dirs)) {
$dirs = [$dirs];
}
$this->compilerOptions['importDirs'] = $dirs;
return $this;
}
/**
* @param string $formatterName
*
* @return $this
*/
public function setFormatter($formatterName)
{
$this->compilerOptions['formatter'] = $formatterName;
return $this;
}
/**
* Sets the compiler.
*
* @param string $compiler
* @param array $options
*
* @return $this
*/
public function compiler($compiler, array $options = [])
{
$this->compiler = $compiler;
$this->compilerOptions = array_merge($this->compilerOptions, $options);
return $this;
}
/**
* Compiles file
*
* @param $file
*
* @return bool|mixed
*/
protected function compile($file)
{
if (is_callable($this->compiler)) {
return call_user_func($this->compiler, $file, $this->compilerOptions);
}
if (method_exists($this, $this->compiler)) {
return $this->{$this->compiler}($file);
}
return false;
}
/**
* {@inheritdoc}
*/
public function run()
{
if (!in_array($this->compiler, $this->compilers, true)
&& !is_callable($this->compiler)
) {
$message = sprintf('Invalid ' . static::FORMAT_NAME . ' compiler %s!', $this->compiler);
return Result::error($this, $message);
}
foreach ($this->files as $in => $out) {
if (!file_exists($in)) {
$message = sprintf('File %s not found.', $in);
return Result::error($this, $message);
}
if (file_exists($out) && !is_writable($out)) {
return Result::error($this, 'Destination already exists and cannot be overwritten.');
}
}
foreach ($this->files as $in => $out) {
$css = $this->compile($in);
if ($css instanceof Result) {
return $css;
} elseif (false === $css) {
$message = sprintf(
ucfirst(static::FORMAT_NAME) . ' compilation failed for %s.',
$in
);
return Result::error($this, $message);
}
$dst = $out . '.part';
$write_result = file_put_contents($dst, $css);
if (false === $write_result) {
$message = sprintf('File write failed: %s', $out);
@unlink($dst);
return Result::error($this, $message);
}
// Cannot be cross-volume: should always succeed
@rename($dst, $out);
$this->printTaskSuccess('Wrote CSS to {filename}', ['filename' => $out]);
}
return Result::success($this, 'All ' . static::FORMAT_NAME . ' files compiled.');
}
}
<?php
namespace Robo\Task\Assets;
use Robo\Result;
use Robo\Exception\TaskException;
use Robo\Task\BaseTask;
use Robo\Task\Base\Exec;
use Symfony\Component\Finder\Finder;
use Symfony\Component\Filesystem\Filesystem as sfFilesystem;
/**
* Minifies images. When the required minifier is not installed on the system
* the task will try to download it from the [imagemin](https://github.com/imagemin) repository.
*
* When the task is run without any specified minifier it will compress the images
* based on the extension.
*
* ```php
* $this->taskImageMinify('assets/images/*')
* ->to('dist/images/')
* ->run();
* ```
*
* This will use the following minifiers:
*
* - PNG: optipng
* - GIF: gifsicle
* - JPG, JPEG: jpegtran
* - SVG: svgo
*
* When the minifier is specified the task will use that for all the input files. In that case
* it is useful to filter the files with the extension:
*
* ```php
* $this->taskImageMinify('assets/images/*.png')
* ->to('dist/images/')
* ->minifier('pngcrush');
* ->run();
* ```
*
* The task supports the following minifiers:
*
* - optipng
* - pngquant
* - advpng
* - pngout
* - zopflipng
* - pngcrush
* - gifsicle
* - jpegoptim
* - jpeg-recompress
* - jpegtran
* - svgo (only minification, no downloading)
*
* You can also specifiy extra options for the minifiers:
*
* ```php
* $this->taskImageMinify('assets/images/*.jpg')
* ->to('dist/images/')
* ->minifier('jpegtran', ['-progressive' => null, '-copy' => 'none'])
* ->run();
* ```
*
* This will execute as:
* `jpegtran -copy none -progressive -optimize -outfile "dist/images/test.jpg" "/var/www/test/assets/images/test.jpg"`
*/
class ImageMinify extends BaseTask
{
/**
* Destination directory for the minified images.
*
* @var string
*/
protected $to;
/**
* Array of the source files.
*
* @var array
*/
protected $dirs = [];
/**
* Symfony 2 filesystem.
*
* @var sfFilesystem
*/
protected $fs;
/**
* Target directory for the downloaded binary executables.
*
* @var string
*/
protected $executableTargetDir;
/**
* Array for the downloaded binary executables.
*
* @var array
*/
protected $executablePaths = [];
/**
* Array for the individual results of all the files.
*
* @var array
*/
protected $results = [];
/**
* Default minifier to use.
*
* @var string
*/
protected $minifier;
/**
* Array for minifier options.
*
* @var array
*/
protected $minifierOptions = [];
/**
* Supported minifiers.
*
* @var array
*/
protected $minifiers = [
// Default 4
'optipng',
'gifsicle',
'jpegtran',
'svgo',
// PNG
'pngquant',
'advpng',
'pngout',
'zopflipng',
'pngcrush',
// JPG
'jpegoptim',
'jpeg-recompress',
];
/**
* Binary repositories of Imagemin.
*
* @link https://github.com/imagemin
*
* @var array
*/
protected $imageminRepos = [
// PNG
'optipng' => 'https://github.com/imagemin/optipng-bin',
'pngquant' => 'https://github.com/imagemin/pngquant-bin',
'advpng' => 'https://github.com/imagemin/advpng-bin',
'pngout' => 'https://github.com/imagemin/pngout-bin',
'zopflipng' => 'https://github.com/imagemin/zopflipng-bin',
'pngcrush' => 'https://github.com/imagemin/pngcrush-bin',
// Gif
'gifsicle' => 'https://github.com/imagemin/gifsicle-bin',
// JPG
'jpegtran' => 'https://github.com/imagemin/jpegtran-bin',
'jpegoptim' => 'https://github.com/imagemin/jpegoptim-bin',
'cjpeg' => 'https://github.com/imagemin/mozjpeg-bin', // note: we do not support this minifier because it creates JPG from non-JPG files
'jpeg-recompress' => 'https://github.com/imagemin/jpeg-recompress-bin',
// WebP
'cwebp' => 'https://github.com/imagemin/cwebp-bin', // note: we do not support this minifier because it creates WebP from non-WebP files
];
public function __construct($dirs)
{
is_array($dirs)
? $this->dirs = $dirs
: $this->dirs[] = $dirs;
$this->fs = new sfFilesystem();
// guess the best path for the executables based on __DIR__
if (($pos = strpos(__DIR__, 'consolidation/robo')) !== false) {
// the executables should be stored in vendor/bin
$this->executableTargetDir = substr(__DIR__, 0, $pos).'bin';
}
// check if the executables are already available
foreach ($this->imageminRepos as $exec => $url) {
$path = $this->executableTargetDir.'/'.$exec;
// if this is Windows add a .exe extension
if (substr($this->getOS(), 0, 3) == 'win') {
$path .= '.exe';
}
if (is_file($path)) {
$this->executablePaths[$exec] = $path;
}
}
}
/**
* {@inheritdoc}
*/
public function run()
{
// find the files
$files = $this->findFiles($this->dirs);
// minify the files
$result = $this->minify($files);
// check if there was an error
if ($result instanceof Result) {
return $result;
}
$amount = (count($files) == 1 ? 'image' : 'images');
$message = "Minified {filecount} out of {filetotal} $amount into {destination}";
$context = ['filecount' => count($this->results['success']), 'filetotal' => count($files), 'destination' => $this->to];
if (count($this->results['success']) == count($files)) {
$this->printTaskSuccess($message, $context);
return Result::success($this, $message, $context);
} else {
return Result::error($this, $message, $context);
}
}
/**
* Sets the target directory where the files will be copied to.
*
* @param string $target
*
* @return $this
*/
public function to($target)
{
$this->to = rtrim($target, '/');
return $this;
}
/**
* Sets the minifier.
*
* @param string $minifier
* @param array $options
*
* @return $this
*/
public function minifier($minifier, array $options = [])
{
$this->minifier = $minifier;
$this->minifierOptions = array_merge($this->minifierOptions, $options);
return $this;
}
/**
* @param array $dirs
*
* @return array|\Robo\Result
*
* @throws \Robo\Exception\TaskException
*/
protected function findFiles($dirs)
{
$files = array();
// find the files
foreach ($dirs as $k => $v) {
// reset finder
$finder = new Finder();
$dir = $k;
$to = $v;
// check if target was given with the to() method instead of key/value pairs
if (is_int($k)) {
$dir = $v;
if (isset($this->to)) {
$to = $this->to;
} else {
throw new TaskException($this, 'target directory is not defined');
}
}
try {
$finder->files()->in($dir);
} catch (\InvalidArgumentException $e) {
// if finder cannot handle it, try with in()->name()
if (strpos($dir, '/') === false) {
$dir = './'.$dir;
}
$parts = explode('/', $dir);
$new_dir = implode('/', array_slice($parts, 0, -1));
try {
$finder->files()->in($new_dir)->name(array_pop($parts));
} catch (\InvalidArgumentException $e) {
return Result::fromException($this, $e);
}
}
foreach ($finder as $file) {
// store the absolute path as key and target as value in the files array
$files[$file->getRealpath()] = $this->getTarget($file->getRealPath(), $to);
}
$fileNoun = count($finder) == 1 ? ' file' : ' files';
$this->printTaskInfo("Found {filecount} $fileNoun in {dir}", ['filecount' => count($finder), 'dir' => $dir]);
}
return $files;
}
/**
* @param string $file
* @param string $to
*
* @return string
*/
protected function getTarget($file, $to)
{
$target = $to.'/'.basename($file);
return $target;
}
/**
* @param array $files
*
* @return \Robo\Result
*/
protected function minify($files)
{
// store the individual results into the results array
$this->results = [
'success' => [],
'error' => [],
];
// loop through the files
foreach ($files as $from => $to) {
if (!isset($this->minifier)) {
// check filetype based on the extension
$extension = strtolower(pathinfo($from, PATHINFO_EXTENSION));
// set the default minifiers based on the extension
switch ($extension) {
case 'png':
$minifier = 'optipng';
break;
case 'jpg':
case 'jpeg':
$minifier = 'jpegtran';
break;
case 'gif':
$minifier = 'gifsicle';
break;
case 'svg':
$minifier = 'svgo';
break;
}
} else {
if (!in_array($this->minifier, $this->minifiers, true)
&& !is_callable(strtr($this->minifier, '-', '_'))
) {
$message = sprintf('Invalid minifier %s!', $this->minifier);
return Result::error($this, $message);
}
$minifier = $this->minifier;
}
// Convert minifier name to camelCase (e.g. jpeg-recompress)
$funcMinifier = $this->camelCase($minifier);
// call the minifier method which prepares the command
if (is_callable($funcMinifier)) {
$command = call_user_func($funcMinifier, $from, $to, $this->minifierOptions);
} elseif (method_exists($this, $funcMinifier)) {
$command = $this->{$funcMinifier}($from, $to);
} else {
$message = sprintf('Minifier method <info>%s</info> cannot be found!', $funcMinifier);
return Result::error($this, $message);
}
// launch the command
$this->printTaskInfo('Minifying {filepath} with {minifier}', ['filepath' => $from, 'minifier' => $minifier]);
$result = $this->executeCommand($command);
// check the return code
if ($result->getExitCode() == 127) {
$this->printTaskError('The {minifier} executable cannot be found', ['minifier' => $minifier]);
// try to install from imagemin repository
if (array_key_exists($minifier, $this->imageminRepos)) {
$result = $this->installFromImagemin($minifier);
if ($result instanceof Result) {
if ($result->wasSuccessful()) {
$this->printTaskSuccess($result->getMessage());
// retry the conversion with the downloaded executable
if (is_callable($minifier)) {
$command = call_user_func($minifier, $from, $to, $minifierOptions);
} elseif (method_exists($this, $minifier)) {
$command = $this->{$minifier}($from, $to);
}
// launch the command
$this->printTaskInfo('Minifying {filepath} with {minifier}', ['filepath' => $from, 'minifier' => $minifier]);
$result = $this->executeCommand($command);
} else {
$this->printTaskError($result->getMessage());
// the download was not successful
return $result;
}
}
} else {
return $result;
}
}
// check the success of the conversion
if ($result->getExitCode() !== 0) {
$this->results['error'][] = $from;
} else {
$this->results['success'][] = $from;
}
}
}
/**
* @return string
*/
protected function getOS()
{
$os = php_uname('s');
$os .= '/'.php_uname('m');
// replace x86_64 to x64, because the imagemin repo uses that
$os = str_replace('x86_64', 'x64', $os);
// replace i386, i686, etc to x86, because of imagemin
$os = preg_replace('/i[0-9]86/', 'x86', $os);
// turn info to lowercase, because of imagemin
$os = strtolower($os);
return $os;
}
/**
* @param string $command
*
* @return \Robo\Result
*/
protected function executeCommand($command)
{
// insert the options into the command
$a = explode(' ', $command);
$executable = array_shift($a);
foreach ($this->minifierOptions as $key => $value) {
// first prepend the value
if (!empty($value)) {
array_unshift($a, $value);
}
// then add the key
if (!is_numeric($key)) {
array_unshift($a, $key);
}
}
// check if the executable can be replaced with the downloaded one
if (array_key_exists($executable, $this->executablePaths)) {
$executable = $this->executablePaths[$executable];
}
array_unshift($a, $executable);
$command = implode(' ', $a);
// execute the command
$exec = new Exec($command);
return $exec->inflect($this)->printed(false)->run();
}
/**
* @param string $executable
*
* @return \Robo\Result
*/
protected function installFromImagemin($executable)
{
// check if there is an url defined for the executable
if (!array_key_exists($executable, $this->imageminRepos)) {
$message = sprintf('The executable %s cannot be found in the defined imagemin repositories', $executable);
return Result::error($this, $message);
}
$this->printTaskInfo('Downloading the {executable} executable from the imagemin repository', ['executable' => $executable]);
$os = $this->getOS();
$url = $this->imageminRepos[$executable].'/blob/master/vendor/'.$os.'/'.$executable.'?raw=true';
if (substr($os, 0, 3) == 'win') {
// if it is win, add a .exe extension
$url = $this->imageminRepos[$executable].'/blob/master/vendor/'.$os.'/'.$executable.'.exe?raw=true';
}
$data = @file_get_contents($url, false, null);
if ($data === false) {
// there is something wrong with the url, try it without the version info
$url = preg_replace('/x[68][64]\//', '', $url);
$data = @file_get_contents($url, false, null);
if ($data === false) {
// there is still something wrong with the url if it is win, try with win32
if (substr($os, 0, 3) == 'win') {
$url = preg_replace('win/', 'win32/', $url);
$data = @file_get_contents($url, false, null);
if ($data === false) {
// there is nothing more we can do
$message = sprintf('Could not download the executable <info>%s</info>', $executable);
return Result::error($this, $message);
}
}
// if it is not windows there is nothing we can do
$message = sprintf('Could not download the executable <info>%s</info>', $executable);
return Result::error($this, $message);
}
}
// check if target directory exists
if (!is_dir($this->executableTargetDir)) {
mkdir($this->executableTargetDir);
}
// save the executable into the target dir
$path = $this->executableTargetDir.'/'.$executable;
if (substr($os, 0, 3) == 'win') {
// if it is win, add a .exe extension
$path = $this->executableTargetDir.'/'.$executable.'.exe';
}
$result = file_put_contents($path, $data);
if ($result === false) {
$message = sprintf('Could not copy the executable <info>%s</info> to %s', $executable, $target_dir);
return Result::error($this, $message);
}
// set the binary to executable
chmod($path, 0755);
// if everything successful, store the executable path
$this->executablePaths[$executable] = $this->executableTargetDir.'/'.$executable;
// if it is win, add a .exe extension
if (substr($os, 0, 3) == 'win') {
$this->executablePaths[$executable] .= '.exe';
}
$message = sprintf('Executable <info>%s</info> successfully downloaded', $executable);
return Result::success($this, $message);
}
/**
* @param string $from
* @param string $to
*
* @return string
*/
protected function optipng($from, $to)
{
$command = sprintf('optipng -quiet -out "%s" -- "%s"', $to, $from);
if ($from != $to && is_file($to)) {
// earlier versions of optipng do not overwrite the target without a backup
// http://sourceforge.net/p/optipng/bugs/37/
unlink($to);
}
return $command;
}
/**
* @param string $from
* @param string $to
*
* @return string
*/
protected function jpegtran($from, $to)
{
$command = sprintf('jpegtran -optimize -outfile "%s" "%s"', $to, $from);
return $command;
}
protected function gifsicle($from, $to)
{
$command = sprintf('gifsicle -o "%s" "%s"', $to, $from);
return $command;
}
/**
* @param string $from
* @param string $to
*
* @return string
*/
protected function svgo($from, $to)
{
$command = sprintf('svgo "%s" "%s"', $from, $to);
return $command;
}
/**
* @param string $from
* @param string $to
*
* @return string
*/
protected function pngquant($from, $to)
{
$command = sprintf('pngquant --force --output "%s" "%s"', $to, $from);
return $command;
}
/**
* @param string $from
* @param string $to
*
* @return string
*/
protected function advpng($from, $to)
{
// advpng does not have any output parameters, copy the file and then compress the copy
$command = sprintf('advpng --recompress --quiet "%s"', $to);
$this->fs->copy($from, $to, true);
return $command;
}
/**
* @param string $from
* @param string $to
*
* @return string
*/
protected function pngout($from, $to)
{
$command = sprintf('pngout -y -q "%s" "%s"', $from, $to);
return $command;
}
/**
* @param string $from
* @param string $to
*
* @return string
*/
protected function zopflipng($from, $to)
{
$command = sprintf('zopflipng -y "%s" "%s"', $from, $to);
return $command;
}
/**
* @param string $from
* @param string $to
*
* @return string
*/
protected function pngcrush($from, $to)
{
$command = sprintf('pngcrush -q -ow "%s" "%s"', $from, $to);
return $command;
}
/**
* @param string $from
* @param string $to
*
* @return string
*/
protected function jpegoptim($from, $to)
{
// jpegoptim only takes the destination directory as an argument
$command = sprintf('jpegoptim --quiet -o --dest "%s" "%s"', dirname($to), $from);
return $command;
}
/**
* @param string $from
* @param string $to
*
* @return string
*/
protected function jpegRecompress($from, $to)
{
$command = sprintf('jpeg-recompress --quiet "%s" "%s"', $from, $to);
return $command;
}
/**
* @param string $text
*
* @return string
*/
public static function camelCase($text)
{
// non-alpha and non-numeric characters become spaces
$text = preg_replace('/[^a-z0-9]+/i', ' ', $text);
$text = trim($text);
// uppercase the first character of each word
$text = ucwords($text);
$text = str_replace(" ", "", $text);
$text = lcfirst($text);
return $text;
}
}
<?php
namespace Robo\Task\Assets;
use Robo\Result;
/**
* Compiles less files.
*
* ```php
* <?php
* $this->taskLess([
* 'less/default.less' => 'css/default.css'
* ])
* ->run();
* ?>
* ```
*
* Use one of both less compilers in your project:
*
* ```
* "leafo/lessphp": "~0.5",
* "oyejorge/less.php": "~1.5"
* ```
*
* Specify directory (string or array) for less imports lookup:
*
* ```php
* <?php
* $this->taskLess([
* 'less/default.less' => 'css/default.css'
* ])
* ->importDir('less')
* ->compiler('lessphp')
* ->run();
* ?>
* ```
*
* You can implement additional compilers by extending this task and adding a
* method named after them and overloading the lessCompilers() method to
* inject the name there.
*/
class Less extends CssPreprocessor
{
const FORMAT_NAME = 'less';
/**
* @var string[]
*/
protected $compilers = [
'less', // https://github.com/oyejorge/less.php
'lessphp', //https://github.com/leafo/lessphp
];
/**
* lessphp compiler
* @link https://github.com/leafo/lessphp
*
* @param string $file
*
* @return string
*/
protected function lessphp($file)
{
if (!class_exists('\lessc')) {
return Result::errorMissingPackage($this, 'lessc', 'leafo/lessphp');
}
$lessCode = file_get_contents($file);
$less = new \lessc();
if (isset($this->compilerOptions['importDirs'])) {
$less->setImportDir($this->compilerOptions['importDirs']);
}
return $less->compile($lessCode);
}
/**
* less compiler
* @link https://github.com/oyejorge/less.php
*
* @param string $file
*
* @return string
*/
protected function less($file)
{
if (!class_exists('\Less_Parser')) {
return Result::errorMissingPackage($this, 'Less_Parser', 'oyejorge/less.php');
}
$lessCode = file_get_contents($file);
$parser = new \Less_Parser();
$parser->SetOptions($this->compilerOptions);
if (isset($this->compilerOptions['importDirs'])) {
$importDirs = [];
foreach ($this->compilerOptions['importDirs'] as $dir) {
$importDirs[$dir] = $dir;
}
$parser->SetImportDirs($importDirs);
}
$parser->parse($lessCode);
return $parser->getCss();
}
}
<?php
namespace Robo\Task\Assets;
trait loadTasks
{
/**
* @param string $input
*
* @return \Robo\Task\Assets\Minify
*/
protected function taskMinify($input)
{
return $this->task(Minify::class, $input);
}
/**
* @param string|string[] $input
*
* @return \Robo\Task\Assets\ImageMinify
*/
protected function taskImageMinify($input)
{
return $this->task(ImageMinify::class, $input);
}
/**
* @param array $input
*
* @return \Robo\Task\Assets\Less
*/
protected function taskLess($input)
{
return $this->task(Less::class, $input);
}
/**
* @param array $input
*
* @return \Robo\Task\Assets\Scss
*/
protected function taskScss($input)
{
return $this->task(Scss::class, $input);
}
}
<?php
namespace Robo\Task\Assets;
use Robo\Result;
use Robo\Task\BaseTask;
/**
* Minifies asset file (CSS or JS).
*
* ``` php
* <?php
* $this->taskMinify( 'web/assets/theme.css' )
* ->run()
* ?>
* ```
* Please install additional dependencies to use:
*
* ```
* "patchwork/jsqueeze": "~1.0",
* "natxet/CssMin": "~3.0"
* ```
*/
class Minify extends BaseTask
{
/**
* @var array
*/
protected $types = ['css', 'js'];
/**
* @var string
*/
protected $text;
/**
* @var string
*/
protected $dst;
/**
* @var string
*/
protected $type;
/**
* @var array
*/
protected $squeezeOptions = [
'singleLine' => true,
'keepImportantComments' => true,
'specialVarRx' => false,
];
/**
* Constructor. Accepts asset file path or string source.
*
* @param string $input
*/
public function __construct($input)
{
if (file_exists($input)) {
$this->fromFile($input);
return;
}
$this->fromText($input);
}
/**
* Sets destination. Tries to guess type from it.
*
* @param string $dst
*
* @return $this
*/
public function to($dst)
{
$this->dst = $dst;
if (!empty($this->dst) && empty($this->type)) {
$this->type($this->getExtension($this->dst));
}
return $this;
}
/**
* Sets type with validation.
*
* @param string $type css|js
*
* @return $this
*/
public function type($type)
{
$type = strtolower($type);
if (in_array($type, $this->types)) {
$this->type = $type;
}
return $this;
}
/**
* Sets text from string source.
*
* @param string $text
*
* @return $this
*/
protected function fromText($text)
{
$this->text = (string)$text;
unset($this->type);
return $this;
}
/**
* Sets text from asset file path. Tries to guess type and set default destination.
*
* @param string $path
*
* @return $this
*/
protected function fromFile($path)
{
$this->text = file_get_contents($path);
unset($this->type);
$this->type($this->getExtension($path));
if (empty($this->dst) && !empty($this->type)) {
$ext_length = strlen($this->type) + 1;
$this->dst = substr($path, 0, -$ext_length) . '.min.' . $this->type;
}
return $this;
}
/**
* Gets file extension from path.
*
* @param string $path
*
* @return string
*/
protected function getExtension($path)
{
return pathinfo($path, PATHINFO_EXTENSION);
}
/**
* Minifies and returns text.
*
* @return string|bool
*/
protected function getMinifiedText()
{
switch ($this->type) {
case 'css':
if (!class_exists('\CssMin')) {
return Result::errorMissingPackage($this, 'CssMin', 'natxet/CssMin');
}
return \CssMin::minify($this->text);
break;
case 'js':
if (!class_exists('\JSqueeze') && !class_exists('\Patchwork\JSqueeze')) {
return Result::errorMissingPackage($this, 'Patchwork\JSqueeze', 'patchwork/jsqueeze');
}
if (class_exists('\JSqueeze')) {
$jsqueeze = new \JSqueeze();
} else {
$jsqueeze = new \Patchwork\JSqueeze();
}
return $jsqueeze->squeeze(
$this->text,
$this->squeezeOptions['singleLine'],
$this->squeezeOptions['keepImportantComments'],
$this->squeezeOptions['specialVarRx']
);
break;
}
return false;
}
/**
* Single line option for the JS minimisation.
*
* @param bool $singleLine
*
* @return $this
*/
public function singleLine($singleLine)
{
$this->squeezeOptions['singleLine'] = (bool)$singleLine;
return $this;
}
/**
* keepImportantComments option for the JS minimisation.
*
* @param bool $keepImportantComments
*
* @return $this
*/
public function keepImportantComments($keepImportantComments)
{
$this->squeezeOptions['keepImportantComments'] = (bool)$keepImportantComments;
return $this;
}
/**
* specialVarRx option for the JS minimisation.
*
* @param bool $specialVarRx
*
* @return $this ;
*/
public function specialVarRx($specialVarRx)
{
$this->squeezeOptions['specialVarRx'] = (bool)$specialVarRx;
return $this;
}
/**
* @return string
*/
public function __toString()
{
return (string) $this->getMinifiedText();
}
/**
* {@inheritdoc}
*/
public function run()
{
if (empty($this->type)) {
return Result::error($this, 'Unknown asset type.');
}
if (empty($this->dst)) {
return Result::error($this, 'Unknown file destination.');
}
if (file_exists($this->dst) && !is_writable($this->dst)) {
return Result::error($this, 'Destination already exists and cannot be overwritten.');
}
$size_before = strlen($this->text);
$minified = $this->getMinifiedText();
if ($minified instanceof Result) {
return $minified;
} elseif (false === $minified) {
return Result::error($this, 'Minification failed.');
}
$size_after = strlen($minified);
// Minification did not reduce file size, so use original file.
if ($size_after > $size_before) {
$minified = $this->text;
$size_after = $size_before;
}
$dst = $this->dst . '.part';
$write_result = file_put_contents($dst, $minified);
if (false === $write_result) {
@unlink($dst);
return Result::error($this, 'File write failed.');
}
// Cannot be cross-volume; should always succeed.
@rename($dst, $this->dst);
if ($size_before === 0) {
$minified_percent = 0;
} else {
$minified_percent = number_format(100 - ($size_after / $size_before * 100), 1);
}
$this->printTaskSuccess('Wrote {filepath}', ['filepath' => $this->dst]);
$context = [
'bytes' => $this->formatBytes($size_after),
'reduction' => $this->formatBytes(($size_before - $size_after)),
'percentage' => $minified_percent,
];
$this->printTaskSuccess('Wrote {bytes} (reduced by {reduction} / {percentage})', $context);
return Result::success($this, 'Asset minified.');
}
}
<?php
namespace Robo\Task\Assets;
use Robo\Result;
/**
* Compiles scss files.
*
* ```php
* <?php
* $this->taskScss([
* 'scss/default.scss' => 'css/default.css'
* ])
* ->importDir('assets/styles')
* ->run();
* ?>
* ```
*
* Use the following scss compiler in your project:
*
* ```
* "leafo/scssphp": "~0.1",
* ```
*
* You can implement additional compilers by extending this task and adding a
* method named after them and overloading the scssCompilers() method to
* inject the name there.
*/
class Scss extends CssPreprocessor
{
const FORMAT_NAME = 'scss';
/**
* @var string[]
*/
protected $compilers = [
'scssphp', // https://github.com/leafo/scssphp
];
/**
* scssphp compiler
* @link https://github.com/leafo/scssphp
*
* @param string $file
*
* @return string
*/
protected function scssphp($file)
{
if (!class_exists('\Leafo\ScssPhp\Compiler')) {
return Result::errorMissingPackage($this, 'scssphp', 'leafo/scssphp');
}
$scssCode = file_get_contents($file);
$scss = new \Leafo\ScssPhp\Compiler();
// set options for the scssphp compiler
if (isset($this->compilerOptions['importDirs'])) {
$scss->setImportPaths($this->compilerOptions['importDirs']);
}
if (isset($this->compilerOptions['formatter'])) {
$scss->setFormatter($this->compilerOptions['formatter']);
}
return $scss->compile($scssCode);
}
/**
* Sets the formatter for scssphp
*
* The method setFormatter($formatterName) sets the current formatter to $formatterName,
* the name of a class as a string that implements the formatting interface. See the source
* for Leafo\ScssPhp\Formatter\Expanded for an example.
*
* Five formatters are included with leafo/scssphp:
* - Leafo\ScssPhp\Formatter\Expanded
* - Leafo\ScssPhp\Formatter\Nested (default)
* - Leafo\ScssPhp\Formatter\Compressed
* - Leafo\ScssPhp\Formatter\Compact
* - Leafo\ScssPhp\Formatter\Crunched
*
* @link http://leafo.github.io/scssphp/docs/#output-formatting
*
* @param string $formatterName
*
* @return $this
*/
public function setFormatter($formatterName)
{
return parent::setFormatter($formatterName);
}
}
<?php
namespace Robo\Task\Base;
use Robo\Contract\CommandInterface;
use Robo\Contract\PrintedInterface;
use Robo\Contract\SimulatedInterface;
use Robo\Task\BaseTask;
use Symfony\Component\Process\Process;
use Robo\Result;
/**
* Executes shell script. Closes it when running in background mode.
*
* ``` php
* <?php
* $this->taskExec('compass')->arg('watch')->run();
* // or use shortcut
* $this->_exec('compass watch');
*
* $this->taskExec('compass watch')->background()->run();
*
* if ($this->taskExec('phpunit .')->run()->wasSuccessful()) {
* $this->say('tests passed');
* }
*
* ?>
* ```
*/
class Exec extends BaseTask implements CommandInterface, PrintedInterface, SimulatedInterface
{
use \Robo\Common\CommandReceiver;
use \Robo\Common\ExecOneCommand;
/**
* @var static[]
*/
protected static $instances = [];
/**
* @var string|\Robo\Contract\CommandInterface
*/
protected $command;
/**
* @var bool
*/
protected $background = false;
/**
* @var null|int
*/
protected $timeout = null;
/**
* @var null|int
*/
protected $idleTimeout = null;
/**
* @var null|array
*/
protected $env = null;
/**
* @var Process
*/
protected $process;
/**
* @param string|\Robo\Contract\CommandInterface $command
*/
public function __construct($command)
{
$this->command = $this->receiveCommand($command);
}
/**
* {@inheritdoc}
*/
public function getCommand()
{
return trim($this->command . $this->arguments);
}
/**
* Executes command in background mode (asynchronously)
*
* @return $this
*/
public function background()
{
self::$instances[] = $this;
$this->background = true;
return $this;
}
/**
* Stop command if it runs longer then $timeout in seconds
*
* @param int $timeout
*
* @return $this
*/
public function timeout($timeout)
{
$this->timeout = $timeout;
return $this;
}
/**
* Stops command if it does not output something for a while
*
* @param int $timeout
*
* @return $this
*/
public function idleTimeout($timeout)
{
$this->idleTimeout = $timeout;
return $this;
}
/**
* Sets the environment variables for the command
*
* @param array $env
*
* @return $this
*/
public function env(array $env)
{
$this->env = $env;
return $this;
}
public function __destruct()
{
$this->stop();
}
protected function stop()
{
if ($this->background && $this->process->isRunning()) {
$this->process->stop();
$this->printTaskInfo("Stopped {command}", ['command' => $this->getCommand()]);
}
}
/**
* @param array $context
*/
protected function printAction($context = [])
{
$command = $this->getCommand();
$dir = $this->workingDirectory ? " in {dir}" : "";
$this->printTaskInfo("Running {command}$dir", ['command' => $command, 'dir' => $this->workingDirectory] + $context);
}
/**
* {@inheritdoc}
*/
public function run()
{
$this->printAction();
$this->process = new Process($this->getCommand());
$this->process->setTimeout($this->timeout);
$this->process->setIdleTimeout($this->idleTimeout);
$this->process->setWorkingDirectory($this->workingDirectory);
if (isset($this->env)) {
$this->process->setEnv($this->env);
}
if (!$this->background and !$this->isPrinted) {
$this->startTimer();
$this->process->run();
$this->stopTimer();
return new Result($this, $this->process->getExitCode(), $this->process->getOutput(), ['time' => $this->getExecutionTime()]);
}
if (!$this->background and $this->isPrinted) {
$this->startTimer();
$this->process->run(
function ($type, $buffer) {
$progressWasVisible = $this->hideTaskProgress();
print($buffer);
$this->showTaskProgress($progressWasVisible);
}
);
$this->stopTimer();
return new Result($this, $this->process->getExitCode(), $this->process->getOutput(), ['time' => $this->getExecutionTime()]);
}
try {
$this->process->start();
} catch (\Exception $e) {
return Result::fromException($this, $e);
}
return Result::success($this);
}
/**
* {@inheritdoc}
*/
public function simulate($context)
{
$this->printAction($context);
}
public static function stopRunningJobs()
{
foreach (self::$instances as $instance) {
if ($instance) {
unset($instance);
}
}
}
}
if (function_exists('pcntl_signal')) {
pcntl_signal(SIGTERM, ['Robo\Task\Base\Exec', 'stopRunningJobs']);
}
register_shutdown_function(['Robo\Task\Base\Exec', 'stopRunningJobs']);
<?php
namespace Robo\Task\Base;
use Robo\Task\CommandStack;
use Robo\Task\Base;
/**
* Execute commands one by one in stack.
* Stack can be stopped on first fail if you call `stopOnFail()`.
*
* ```php
* <?php
* $this->taskExecStack()
* ->stopOnFail()
* ->exec('mkdir site')
* ->exec('cd site')
* ->run();
*
* ?>
* ```
*
* @method $this stopOnFail()
*/
class ExecStack extends CommandStack
{
}
<?php
namespace Robo\Task\Base;
trait loadShortcuts
{
/**
* Executes shell command
*
* @param string|\Robo\Contract\CommandInterface $command
*
* @return \Robo\Result
*/
protected function _exec($command)
{
return $this->taskExec($command)->run();
}
}
<?php
namespace Robo\Task\Base;
trait loadTasks
{
/**
* @param string|\Robo\Contract\CommandInterface $command
*
* @return Exec
*/
protected function taskExec($command)
{
return $this->task(Exec::class, $command);
}
protected function taskExecStack()
{
return $this->task(ExecStack::class);
}
/**
* @return ParallelExec
*/
protected function taskParallelExec()
{
return $this->task(ParallelExec::class);
}
/**
* @param $command
* @return SymfonyCommand
*/
protected function taskSymfonyCommand($command)
{
return $this->task(SymfonyCommand::class, $command);
}
/**
* @return Watch
*/
protected function taskWatch()
{
return $this->task(Watch::class, $this);
}
}
<?php
namespace Robo\Task\Base;
use Robo\Contract\ProgressIndicatorAwareInterface;
use Robo\Common\ProgressIndicatorAwareTrait;
use Robo\Contract\CommandInterface;
use Robo\Contract\PrintedInterface;
use Robo\Result;
use Robo\Task\BaseTask;
use Symfony\Component\Process\Exception\ProcessTimedOutException;
use Symfony\Component\Process\Process;
/**
* Class ParallelExecTask
*
* ``` php
* <?php
* $this->taskParallelExec()
* ->process('php ~/demos/script.php hey')
* ->process('php ~/demos/script.php hoy')
* ->process('php ~/demos/script.php gou')
* ->run();
* ?>
* ```
*
*
* @method \Robo\Task\Base\ParallelExec timeout(int $timeout) stops process if it runs longer then `$timeout` (seconds)
* @method \Robo\Task\Base\ParallelExec idleTimeout(int $timeout) stops process if it does not output for time longer then `$timeout` (seconds)
*/
class ParallelExec extends BaseTask implements CommandInterface, PrintedInterface
{
use \Robo\Common\CommandReceiver;
/**
* @var Process[]
*/
protected $processes = [];
/**
* @var null|int
*/
protected $timeout = null;
/**
* @var null|int
*/
protected $idleTimeout = null;
/**
* @var bool
*/
protected $isPrinted = false;
/**
* {@inheritdoc}
*/
public function getPrinted()
{
return $this->isPrinted;
}
/**
* @param bool $isPrinted
*
* @return $this
*/
public function printed($isPrinted = true)
{
$this->isPrinted = $isPrinted;
return $this;
}
/**
* @param string|\Robo\Contract\CommandInterface $command
*
* @return $this
*/
public function process($command)
{
$this->processes[] = new Process($this->receiveCommand($command));
return $this;
}
/**
* @param int $timeout
*
* @return $this
*/
public function timeout($timeout)
{
$this->timeout = $timeout;
return $this;
}
/**
* @param int $idleTimeout
*
* @return $this
*/
public function idleTimeout($idleTimeout)
{
$this->idleTimeout = $idleTimeout;
return $this;
}
/**
* {@inheritdoc}
*/
public function getCommand()
{
return implode(' && ', $this->processes);
}
/**
* @return int
*/
public function progressIndicatorSteps()
{
return count($this->processes);
}
/**
* {@inheritdoc}
*/
public function run()
{
foreach ($this->processes as $process) {
$process->setIdleTimeout($this->idleTimeout);
$process->setTimeout($this->timeout);
$process->start();
$this->printTaskInfo($process->getCommandLine());
}
$this->startProgressIndicator();
$running = $this->processes;
while (true) {
foreach ($running as $k => $process) {
try {
$process->checkTimeout();
} catch (ProcessTimedOutException $e) {
$this->printTaskWarning("Process timed out for {command}", ['command' => $process->getCommandLine(), '_style' => ['command' => 'fg=white;bg=magenta']]);
}
if (!$process->isRunning()) {
$this->advanceProgressIndicator();
if ($this->isPrinted) {
$this->printTaskInfo("Output for {command}:\n\n{output}", ['command' => $process->getCommandLine(), 'output' => $process->getOutput(), '_style' => ['command' => 'fg=white;bg=magenta']]);
$errorOutput = $process->getErrorOutput();
if ($errorOutput) {
$this->printTaskError(rtrim($errorOutput));
}
}
unset($running[$k]);
}
}
if (empty($running)) {
break;
}
usleep(1000);
}
$this->stopProgressIndicator();
$errorMessage = '';
$exitCode = 0;
foreach ($this->processes as $p) {
if ($p->getExitCode() === 0) {
continue;
}
$errorMessage .= "'" . $p->getCommandLine() . "' exited with code ". $p->getExitCode()." \n";
$exitCode = max($exitCode, $p->getExitCode());
}
if (!$errorMessage) {
$this->printTaskSuccess('{process-count} processes finished running', ['process-count' => count($this->processes)]);
}
return new Result($this, $exitCode, $errorMessage, ['time' => $this->getExecutionTime()]);
}
}
<?php
namespace Robo\Task\Base;
use Robo\Robo;
use Robo\Result;
use Robo\Task\BaseTask;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputInterface;
/**
* Executes Symfony Command
*
* ``` php
* <?php
* // Symfony Command
* $this->taskSymfonyCommand(new \Codeception\Command\Run('run'))
* ->arg('suite','acceptance')
* ->opt('debug')
* ->run();
*
* // Artisan Command
* $this->taskSymfonyCommand(new ModelGeneratorCommand())
* ->arg('name', 'User')
* ->run();
* ?>
* ```
*/
class SymfonyCommand extends BaseTask
{
/**
* @var \Symfony\Component\Console\Command\Command
*/
protected $command;
/**
* @var string[]
*/
protected $input;
public function __construct(Command $command)
{
$this->command = $command;
$this->input = [];
}
/**
* @param string $arg
* @param string $value
*
* @return $this
*/
public function arg($arg, $value)
{
$this->input[$arg] = $value;
return $this;
}
public function opt($option, $value = null)
{
$this->input["--$option"] = $value;
return $this;
}
/**
* {@inheritdoc}
*/
public function run()
{
$this->printTaskInfo('Running command {command}', ['command' => $this->command->getName()]);
return new Result(
$this,
$this->command->run(new ArrayInput($this->input), Robo::output())
);
}
}
<?php
namespace Robo\Task\Base;
use Lurker\Event\FilesystemEvent;
use Lurker\ResourceWatcher;
use Robo\Result;
use Robo\Task\BaseTask;
/**
* Runs task when specified file or dir was changed.
* Uses Lurker library.
*
* ``` php
* <?php
* $this->taskWatch()
* ->monitor('composer.json', function() {
* $this->taskComposerUpdate()->run();
* })->monitor('src', function() {
* $this->taskExec('phpunit')->run();
* })->run();
* ?>
* ```
*/
class Watch extends BaseTask
{
/**
* @var \Closure
*/
protected $closure;
/**
* @var array
*/
protected $monitor = [];
/**
* @var object
*/
protected $bindTo;
/**
* @param $bindTo
*/
public function __construct($bindTo)
{
$this->bindTo = $bindTo;
}
/**
* @param string|string[] $paths
* @param \Closure $callable
*
* @return $this
*/
public function monitor($paths, \Closure $callable)
{
if (!is_array($paths)) {
$paths = [$paths];
}
$this->monitor[] = [$paths, $callable];
return $this;
}
/**
* {@inheritdoc}
*/
public function run()
{
if (!class_exists('Lurker\\ResourceWatcher')) {
return Result::errorMissingPackage($this, 'ResourceWatcher', 'henrikbjorn/lurker');
}
$watcher = new ResourceWatcher();
foreach ($this->monitor as $k => $monitor) {
/** @var \Closure $closure */
$closure = $monitor[1];
$closure->bindTo($this->bindTo);
foreach ($monitor[0] as $i => $dir) {
$watcher->track("fs.$k.$i", $dir, FilesystemEvent::MODIFY);
$this->printTaskInfo('Watching {dir} for changes...', ['dir' => $dir]);
$watcher->addListener("fs.$k.$i", $closure);
}
}
$watcher->start();
return Result::success($this);
}
}
<?php
namespace Robo\Task;
use Robo\Common\InflectionTrait;
use Robo\Contract\InflectionInterface;
use Robo\Common\TaskIO;
use Robo\Contract\TaskInterface;
use Robo\Contract\ProgressIndicatorAwareInterface;
use Robo\Common\ProgressIndicatorAwareTrait;
use Robo\Contract\ConfigAwareInterface;
use Psr\Log\LoggerAwareInterface;
abstract class BaseTask implements TaskInterface, LoggerAwareInterface, ConfigAwareInterface, ProgressIndicatorAwareInterface, InflectionInterface
{
use TaskIO; // uses LoggerAwareTrait and ConfigAwareTrait
use ProgressIndicatorAwareTrait;
use InflectionTrait;
/**
* {@inheritdoc}
*/
public function injectDependencies(InflectionInterface $child)
{
if ($child instanceof LoggerAwareInterface && $this->logger) {
$child->setLogger($this->logger);
}
if ($child instanceof ProgressIndicatorAwareInterface && $this->progressIndicator) {
$child->setProgressIndicator($this->progressIndicator);
}
if ($child instanceof ConfigAwareInterface && $this->getConfig()) {
$child->setConfig($this->getConfig());
}
}
}
<?php
namespace Robo\Task\Bower;
use Robo\Task\BaseTask;
use Robo\Exception\TaskException;
abstract class Base extends BaseTask
{
use \Robo\Common\ExecOneCommand;
protected $opts = [];
protected $action = '';
/**
* @var string
*/
protected $command = '';
/**
* adds `allow-root` option to bower
*
* @return $this
*/
public function allowRoot()
{
$this->option('allow-root');
return $this;
}
/**
* adds `force-latest` option to bower
*
* @return $this
*/
public function forceLatest()
{
$this->option('force-latest');
return $this;
}
/**
* adds `production` option to bower
*
* @return $this
*/
public function noDev()
{
$this->option('production');
return $this;
}
/**
* adds `offline` option to bower
*
* @return $this
*/
public function offline()
{
$this->option('offline');
return $this;
}
/**
* Base constructor.
*
* @param null|string $pathToBower
*
* @throws \Robo\Exception\TaskException
*/
public function __construct($pathToBower = null)
{
$this->command = $pathToBower;
if (!$this->command) {
$this->command = $this->findExecutable('bower');
}
if (!$this->command) {
throw new TaskException(__CLASS__, "Bower executable not found.");
}
}
/**
* @return string
*/
public function getCommand()
{
return "{$this->command} {$this->action}{$this->arguments}";
}
}
<?php
namespace Robo\Task\Bower;
use Robo\Task\Bower;
use Robo\Contract\CommandInterface;
/**
* Bower Install
*
* ``` php
* <?php
* // simple execution
* $this->taskBowerInstall()->run();
*
* // prefer dist with custom path
* $this->taskBowerInstall('path/to/my/bower')
* ->noDev()
* ->run();
* ?>
* ```
*/
class Install extends Base implements CommandInterface
{
/**
* {@inheritdoc}
*/
protected $action = 'install';
/**
* {@inheritdoc}
*/
public function run()
{
$this->printTaskInfo('Install Bower packages: {arguments}', ['arguments' => $this->arguments]);
return $this->executeCommand($this->getCommand());
}
}
<?php
namespace Robo\Task\Bower;
trait loadTasks
{
/**
* @param null|string $pathToBower
*
* @return Install
*/
protected function taskBowerInstall($pathToBower = null)
{
return $this->task(Install::class, $pathToBower);
}
/**
* @param null|string $pathToBower
*
* @return Update
*/
protected function taskBowerUpdate($pathToBower = null)
{
return $this->task(Update::class, $pathToBower);
}
}
<?php
namespace Robo\Task\Bower;
use Robo\Task\Bower;
/**
* Bower Update
*
* ``` php
* <?php
* // simple execution
* $this->taskBowerUpdate->run();
*
* // prefer dist with custom path
* $this->taskBowerUpdate('path/to/my/bower')
* ->noDev()
* ->run();
* ?>
* ```
*/
class Update extends Base
{
/**
* {@inheritdoc}
*/
protected $action = 'update';
/**
* {@inheritdoc}
*/
public function run()
{
$this->printTaskInfo('Update Bower packages: {arguments}', ['arguments' => $this->arguments]);
return $this->executeCommand($this->getCommand());
}
}
<?php
namespace Robo\Task;
use Robo\Common\ExecCommand;
use Robo\Contract\PrintedInterface;
use Robo\Result;
use Robo\Contract\CommandInterface;
use Robo\Common\DynamicParams;
use Robo\Exception\TaskException;
abstract class CommandStack extends BaseTask implements CommandInterface, PrintedInterface
{
use ExecCommand;
/**
* @var string
*/
protected $executable;
protected $result;
/**
* @var string[]
*/
protected $exec = [];
/**
* @var bool
*/
protected $stopOnFail = false;
/**
* {@inheritdoc}
*/
public function getCommand()
{
return implode(' && ', $this->exec);
}
/**
* @param string $executable
*
* @return $this
*/
public function executable($executable)
{
$this->executable = $executable;
return $this;
}
/**
* @param string|string[] $command
*
* @return $this
*/
public function exec($command)
{
if (is_array($command)) {
$command = implode(' ', array_filter($command));
}
$command = $this->executable . ' ' . $this->stripExecutableFromCommand($command);
array_push($this->exec, trim($command));
return $this;
}
/**
* @param bool $stopOnFail
*
* @return $this
*/
public function stopOnFail($stopOnFail = true)
{
$this->stopOnFail = $stopOnFail;
return $this;
}
public function result($result)
{
$this->result = $result;
return $this;
}
/**
* @param string $command
*
* @return string
*/
protected function stripExecutableFromCommand($command)
{
$command = trim($command);
$executable = $this->executable . ' ';
if (strpos($command, $executable) === 0) {
$command = substr($command, strlen($executable));
}
return $command;
}
/**
* {@inheritdoc}
*/
public function run()
{
if (empty($this->exec)) {
throw new TaskException($this, 'You must add at least one command');
}
if (!$this->stopOnFail) {
$this->printTaskInfo('{command}', ['command' => $this->getCommand()]);
return $this->executeCommand($this->getCommand());
}
foreach ($this->exec as $command) {
$this->printTaskInfo("Executing {command}", ['command' => $command]);
$result = $this->executeCommand($command);
if (!$result->wasSuccessful()) {
return $result;
}
}
return Result::success($this);
}
}
<?php
namespace Robo\Task\Composer;
use Robo\Robo;
use Robo\Task\BaseTask;
use Robo\Exception\TaskException;
abstract class Base extends BaseTask
{
use \Robo\Common\ExecOneCommand;
/**
* @var string
*/
protected $command = '';
/**
* @var string
*/
protected $prefer;
/**
* @var string
*/
protected $dev;
/**
* @var string
*/
protected $optimizeAutoloader;
/**
* @var string
*/
protected $ansi;
/**
* @var string
*/
protected $dir;
/**
* Action to use
*
* @var string
*/
protected $action = '';
/**
* adds `prefer-dist` option to composer
*
* @return $this
*/
public function preferDist()
{
$this->prefer = '--prefer-dist';
return $this;
}
/**
* adds `prefer-source` option to composer
*
* @return $this
*/
public function preferSource()
{
$this->prefer = '--prefer-source';
return $this;
}
/**
* adds `no-dev` option to composer
*
* @return $this
*/
public function noDev()
{
$this->dev = '--no-dev';
return $this;
}
/**
* adds `no-ansi` option to composer
*
* @return $this
*/
public function noAnsi()
{
$this->ansi = '--no-ansi';
return $this;
}
/**
* adds `ansi` option to composer
*
* @return $this
*/
public function ansi()
{
$this->ansi = '--ansi';
return $this;
}
/**
* adds `optimize-autoloader` option to composer
*
* @return $this
*/
public function optimizeAutoloader()
{
$this->optimizeAutoloader = '--optimize-autoloader';
return $this;
}
/**
* @param null|string $pathToComposer
*
* @throws \Robo\Exception\TaskException
*/
public function __construct($pathToComposer = null)
{
$this->command = $pathToComposer;
if (!$this->command) {
$this->command = $this->findExecutablePhar('composer');
}
if (!$this->command) {
throw new TaskException(__CLASS__, "Neither local composer.phar nor global composer installation could be found.");
}
}
/**
* {@inheritdoc}
*/
public function getCommand()
{
if (!isset($this->ansi) && $this->getConfig()->isDecorated()) {
$this->ansi();
}
$this->option($this->prefer)
->option($this->dev)
->option($this->optimizeAutoloader)
->option($this->ansi);
return "{$this->command} {$this->action}{$this->arguments}";
}
}
<?php
namespace Robo\Task\Composer;
/**
* Composer Dump Autoload
*
* ``` php
* <?php
* // simple execution
* $this->taskComposerDumpAutoload()->run();
*
* // dump auto loader with custom path
* $this->taskComposerDumpAutoload('path/to/my/composer.phar')
* ->preferDist()
* ->run();
*
* // optimize autoloader dump with custom path
* $this->taskComposerDumpAutoload('path/to/my/composer.phar')
* ->optimize()
* ->run();
*
* // optimize autoloader dump with custom path and no dev
* $this->taskComposerDumpAutoload('path/to/my/composer.phar')
* ->optimize()
* ->noDev()
* ->run();
* ?>
* ```
*/
class DumpAutoload extends Base
{
/**
* {@inheritdoc}
*/
protected $action = 'dump-autoload';
/**
* @var string
*/
protected $optimize;
/**
* @return $this
*/
public function optimize()
{
$this->optimize = "--optimize";
return $this;
}
/**
* {@inheritdoc}
*/
public function getCommand()
{
$this->option($this->optimize);
return parent::getCommand();
}
/**
* {@inheritdoc}
*/
public function run()
{
$command = $this->getCommand();
$this->printTaskInfo('Dumping Autoloader: {command}', ['command' => $command]);
return $this->executeCommand($command);
}
}
<?php
namespace Robo\Task\Composer;
/**
* Composer Install
*
* ``` php
* <?php
* // simple execution
* $this->taskComposerInstall()->run();
*
* // prefer dist with custom path
* $this->taskComposerInstall('path/to/my/composer.phar')
* ->preferDist()
* ->run();
*
* // optimize autoloader with custom path
* $this->taskComposerInstall('path/to/my/composer.phar')
* ->optimizeAutoloader()
* ->run();
* ?>
* ```
*/
class Install extends Base
{
/**
* {@inheritdoc}
*/
protected $action = 'install';
/**
* {@inheritdoc}
*/
public function run()
{
$command = $this->getCommand();
$this->printTaskInfo('Installing Packages: {command}', ['command' => $command]);
return $this->executeCommand($command);
}
}
<?php
namespace Robo\Task\Composer;
trait loadTasks
{
/**
* @param null|string $pathToComposer
*
* @return Install
*/
protected function taskComposerInstall($pathToComposer = null)
{
return $this->task(Install::class, $pathToComposer);
}
/**
* @param null|string $pathToComposer
*
* @return Update
*/
protected function taskComposerUpdate($pathToComposer = null)
{
return $this->task(Update::class, $pathToComposer);
}
/**
* @param null|string $pathToComposer
*
* @return DumpAutoload
*/
protected function taskComposerDumpAutoload($pathToComposer = null)
{
return $this->task(DumpAutoload::class, $pathToComposer);
}
/**
* @param null|string $pathToComposer
*
* @return Validate
*/
protected function taskComposerValidate($pathToComposer = null)
{
return $this->task(Validate::class, $pathToComposer);
}
/**
* @param null|string $pathToComposer
*
* @return Remove
*/
protected function taskComposerRemove($pathToComposer = null)
{
return $this->task(Remove::class, $pathToComposer);
}
}
<?php
namespace Robo\Task\Composer;
/**
* Composer Validate
*
* ``` php
* <?php
* // simple execution
* $this->taskComposerValidate()->run();
* ?>
* ```
*/
class Remove extends Base
{
/**
* {@inheritdoc}
*/
protected $action = 'remove';
/**
* @return $this
*/
public function dev()
{
$this->option('--dev');
return $this;
}
/**
* @return $this
*/
public function noProgress()
{
$this->option('--no-progress');
return $this;
}
/**
* @return $this
*/
public function noUpdate()
{
$this->option('--no-update');
return $this;
}
/**
* @return $this
*/
public function updateNoDev()
{
$this->option('--update-no-dev');
return $this;
}
/**
* @return $this
*/
public function noUpdateWithDependencies()
{
$this->option('--no-update-with-dependencies');
return $this;
}
/**
* {@inheritdoc}
*/
public function run()
{
$command = $this->getCommand();
$this->printTaskInfo('Removing packages: {command}', ['command' => $command]);
return $this->executeCommand($command);
}
}
<?php
namespace Robo\Task\Composer;
/**
* Composer Update
*
* ``` php
* <?php
* // simple execution
* $this->taskComposerUpdate()->run();
*
* // prefer dist with custom path
* $this->taskComposerUpdate('path/to/my/composer.phar')
* ->preferDist()
* ->run();
*
* // optimize autoloader with custom path
* $this->taskComposerUpdate('path/to/my/composer.phar')
* ->optimizeAutoloader()
* ->run();
* ?>
* ```
*/
class Update extends Base
{
/**
* {@inheritdoc}
*/
protected $action = 'update';
/**
* {@inheritdoc}
*/
public function run()
{
$command = $this->getCommand();
$this->printTaskInfo('Updating Packages: {command}', ['command' => $command]);
return $this->executeCommand($command);
}
}
<?php
namespace Robo\Task\Composer;
/**
* Composer Validate
*
* ``` php
* <?php
* // simple execution
* $this->taskComposerValidate()->run();
* ?>
* ```
*/
class Validate extends Base
{
/**
* {@inheritdoc}
*/
protected $action = 'validate';
/**
* @var string
*/
protected $noCheckAll;
/**
* @var string
*/
protected $noCheckLock;
/**
* @var string
*/
protected $noCheckPublish;
/**
* @var string
*/
protected $withDependencies;
/**
* @var string
*/
protected $strict;
/**
* @return $this
*/
public function noCheckAll()
{
$this->noCheckAll = '--no-check-all';
return $this;
}
/**
* @return $this
*/
public function noCheckLock()
{
$this->noCheckLock = '--no-check-lock';
return $this;
}
/**
* @return $this
*/
public function noCheckPublish()
{
$this->noCheckPublish = '--no-check-publish';
return $this;
}
/**
* @return $this
*/
public function withDependencies()
{
$this->withDependencies = '--with-dependencies';
return $this;
}
/**
* @return $this
*/
public function strict()
{
$this->strict = '--strict';
return $this;
}
/**
* {@inheritdoc}
*/
public function getCommand()
{
$this->option($this->noCheckAll);
$this->option($this->noCheckLock);
$this->option($this->noCheckPublish);
$this->option($this->withDependencies);
$this->option($this->strict);
return parent::getCommand();
}
/**
* {@inheritdoc}
*/
public function run()
{
$command = $this->getCommand();
$this->printTaskInfo('Validating composer.json: {command}', ['command' => $command]);
return $this->executeCommand($command);
}
}
<?php
namespace Robo\Task\Development;
use Robo\Task\BaseTask;
use Robo\Task\File\Replace;
use Robo\Task\Filesystem;
use Robo\Result;
use Robo\Task\Development;
use Robo\Contract\BuilderAwareInterface;
use Robo\Common\BuilderAwareTrait;
/**
* Helps to manage changelog file.
* Creates or updates `changelog.md` file with recent changes in current version.
*
* ``` php
* <?php
* $version = "0.1.0";
* $this->taskChangelog()
* ->version($version)
* ->change("released to github")
* ->run();
* ?>
* ```
*
* Changes can be asked from Console
*
* ``` php
* <?php
* $this->taskChangelog()
* ->version($version)
* ->askForChanges()
* ->run();
* ?>
* ```
*
* @method Development\Changelog filename(string $filename)
* @method Development\Changelog anchor(string $anchor)
* @method Development\Changelog version(string $version)
*/
class Changelog extends BaseTask implements BuilderAwareInterface
{
use BuilderAwareTrait;
/**
* @var string
*/
protected $filename;
/**
* @var array
*/
protected $log = [];
/**
* @var string
*/
protected $anchor = "# Changelog";
/**
* @var string
*/
protected $version = "";
/**
* @param string $filename
*
* @return $this
*/
public function filename($filename)
{
$this->filename = $filename;
return $this;
}
/**
* @param string $item
*
* @return $this
*/
public function log($item)
{
$this->log[] = $item;
return $this;
}
/**
* @param string $anchor
*
* @return $this
*/
public function anchor($anchor)
{
$this->anchor = $anchor;
return $this;
}
/**
* @param string $version
*
* @return $this
*/
public function version($version)
{
$this->version = $version;
return $this;
}
/**
* @param string $filename
*/
public function __construct($filename)
{
$this->filename = $filename;
}
/**
* @param array $data
*
* @return $this
*/
public function changes(array $data)
{
$this->log = array_merge($this->log, $data);
return $this;
}
/**
* @param string $change
*
* @return $this
*/
public function change($change)
{
$this->log[] = $change;
return $this;
}
/**
* @return array
*/
public function getChanges()
{
return $this->log;
}
/**
* {@inheritdoc}
*/
public function run()
{
if (empty($this->log)) {
return Result::error($this, "Changelog is empty");
}
$text = implode(
"\n",
array_map(
function ($i) {
return "* $i *" . date('Y-m-d') . "*";
},
$this->log
)
) . "\n";
$ver = "#### {$this->version}\n\n";
$text = $ver . $text;
if (!file_exists($this->filename)) {
$this->printTaskInfo('Creating {filename}', ['filename' => $this->filename]);
$res = file_put_contents($this->filename, $this->anchor);
if ($res === false) {
return Result::error($this, "File {filename} cant be created", ['filename' => $this->filename]);
}
}
/** @var \Robo\Result $result */
// trying to append to changelog for today
$result = $this->collectionBuilder()->taskReplace($this->filename)
->from($ver)
->to($text)
->run();
if (!isset($result['replaced']) || !$result['replaced']) {
$result = $this->collectionBuilder()->taskReplace($this->filename)
->from($this->anchor)
->to($this->anchor . "\n\n" . $text)
->run();
}
return new Result($this, $result->getExitCode(), $result->getMessage(), $this->log);
}
}
<?php
namespace Robo\Task\Development;
use Robo\Task\BaseTask;
use Robo\Task\File\Write;
use Robo\Task\Filesystem;
use Robo\Result;
use Robo\Task\Development;
use Robo\Contract\BuilderAwareInterface;
use Robo\Common\BuilderAwareTrait;
/**
* Simple documentation generator from source files.
* Takes classes, properties and methods with their docblocks and writes down a markdown file.
*
* ``` php
* <?php
* $this->taskGenDoc('models.md')
* ->docClass('Model\User') // take class Model\User
* ->docClass('Model\Post') // take class Model\Post
* ->filterMethods(function(\ReflectionMethod $r) {
* return $r->isPublic() or $r->isProtected(); // process public and protected methods
* })->processClass(function(\ReflectionClass $r, $text) {
* return "Class ".$r->getName()."\n\n$text\n\n###Methods\n";
* })->run();
* ```
*
* By default this task generates a documentation for each public method of a class.
* It combines method signature with a docblock. Both can be post-processed.
*
* ``` php
* <?php
* $this->taskGenDoc('models.md')
* ->docClass('Model\User')
* ->processClassSignature(false) // false can be passed to not include class signature
* ->processClassDocBlock(function(\ReflectionClass $r, $text) {
* return "[This is part of application model]\n" . $text;
* })->processMethodSignature(function(\ReflectionMethod $r, $text) {
* return "#### {$r->name}()";
* })->processMethodDocBlock(function(\ReflectionMethod $r, $text) {
* return strpos($r->name, 'save')===0 ? "[Saves to the database]\n" . $text : $text;
* })->run();
* ```
*
* @method \Robo\Task\Development\GenerateMarkdownDoc docClass(string $classname) put a class you want to be documented
* @method \Robo\Task\Development\GenerateMarkdownDoc filterMethods(\Closure $func) using callback function filter out methods that won't be documented
* @method \Robo\Task\Development\GenerateMarkdownDoc filterClasses(\Closure $func) using callback function filter out classes that won't be documented
* @method \Robo\Task\Development\GenerateMarkdownDoc filterProperties(\Closure $func) using callback function filter out properties that won't be documented
* @method \Robo\Task\Development\GenerateMarkdownDoc processClass(\Closure $func) post-process class documentation
* @method \Robo\Task\Development\GenerateMarkdownDoc processClassSignature(\Closure $func) post-process class signature. Provide *false* to skip.
* @method \Robo\Task\Development\GenerateMarkdownDoc processClassDocBlock(\Closure $func) post-process class docblock contents. Provide *false* to skip.
* @method \Robo\Task\Development\GenerateMarkdownDoc processMethod(\Closure $func) post-process method documentation. Provide *false* to skip.
* @method \Robo\Task\Development\GenerateMarkdownDoc processMethodSignature(\Closure $func) post-process method signature. Provide *false* to skip.
* @method \Robo\Task\Development\GenerateMarkdownDoc processMethodDocBlock(\Closure $func) post-process method docblock contents. Provide *false* to skip.
* @method \Robo\Task\Development\GenerateMarkdownDoc processProperty(\Closure $func) post-process property documentation. Provide *false* to skip.
* @method \Robo\Task\Development\GenerateMarkdownDoc processPropertySignature(\Closure $func) post-process property signature. Provide *false* to skip.
* @method \Robo\Task\Development\GenerateMarkdownDoc processPropertyDocBlock(\Closure $func) post-process property docblock contents. Provide *false* to skip.
* @method \Robo\Task\Development\GenerateMarkdownDoc reorder(\Closure $func) use a function to reorder classes
* @method \Robo\Task\Development\GenerateMarkdownDoc reorderMethods(\Closure $func) use a function to reorder methods in class
* @method \Robo\Task\Development\GenerateMarkdownDoc prepend($text) inserts text into beginning of markdown file
* @method \Robo\Task\Development\GenerateMarkdownDoc append($text) inserts text in the end of markdown file
*/
class GenerateMarkdownDoc extends BaseTask implements BuilderAwareInterface
{
use BuilderAwareTrait;
/**
* @var string[]
*/
protected $docClass = [];
/**
* @var callable
*/
protected $filterMethods;
/**
* @var callable
*/
protected $filterClasses;
/**
* @var callable
*/
protected $filterProperties;
/**
* @var callable
*/
protected $processClass;
/**
* @var callable|false
*/
protected $processClassSignature;
/**
* @var callable|false
*/
protected $processClassDocBlock;
/**
* @var callable|false
*/
protected $processMethod;
/**
* @var callable|false
*/
protected $processMethodSignature;
/**
* @var callable|false
*/
protected $processMethodDocBlock;
/**
* @var callable|false
*/
protected $processProperty;
/**
* @var callable|false
*/
protected $processPropertySignature;
/**
* @var callable|false
*/
protected $processPropertyDocBlock;
/**
* @var callable
*/
protected $reorder;
/**
* @var callable
*/
protected $reorderMethods;
/**
* @todo Unused property.
*
* @var callable
*/
protected $reorderProperties;
/**
* @var string
*/
protected $filename;
/**
* @var string
*/
protected $prepend = "";
/**
* @var string
*/
protected $append = "";
/**
* @var string
*/
protected $text;
/**
* @var string[]
*/
protected $textForClass = [];
/**
* @param string $filename
*
* @return static
*/
public static function init($filename)
{
return new static($filename);
}
/**
* @param string $filename
*/
public function __construct($filename)
{
$this->filename = $filename;
}
/**
* @param string $item
*
* @return $this
*/
public function docClass($item)
{
$this->docClass[] = $item;
return $this;
}
/**
* @param callable $filterMethods
*
* @return $this
*/
public function filterMethods($filterMethods)
{
$this->filterMethods = $filterMethods;
return $this;
}
/**
* @param callable $filterClasses
*
* @return $this
*/
public function filterClasses($filterClasses)
{
$this->filterClasses = $filterClasses;
return $this;
}
/**
* @param callable $filterProperties
*
* @return $this
*/
public function filterProperties($filterProperties)
{
$this->filterProperties = $filterProperties;
return $this;
}
/**
* @param callable $processClass
*
* @return $this
*/
public function processClass($processClass)
{
$this->processClass = $processClass;
return $this;
}
/**
* @param callable|false $processClassSignature
*
* @return $this
*/
public function processClassSignature($processClassSignature)
{
$this->processClassSignature = $processClassSignature;
return $this;
}
/**
* @param callable|false $processClassDocBlock
*
* @return $this
*/
public function processClassDocBlock($processClassDocBlock)
{
$this->processClassDocBlock = $processClassDocBlock;
return $this;
}
/**
* @param callable|false $processMethod
*
* @return $this
*/
public function processMethod($processMethod)
{
$this->processMethod = $processMethod;
return $this;
}
/**
* @param callable|false $processMethodSignature
*
* @return $this
*/
public function processMethodSignature($processMethodSignature)
{
$this->processMethodSignature = $processMethodSignature;
return $this;
}
/**
* @param callable|false $processMethodDocBlock
*
* @return $this
*/
public function processMethodDocBlock($processMethodDocBlock)
{
$this->processMethodDocBlock = $processMethodDocBlock;
return $this;
}
/**
* @param callable|false $processProperty
*
* @return $this
*/
public function processProperty($processProperty)
{
$this->processProperty = $processProperty;
return $this;
}
/**
* @param callable|false $processPropertySignature
*
* @return $this
*/
public function processPropertySignature($processPropertySignature)
{
$this->processPropertySignature = $processPropertySignature;
return $this;
}
/**
* @param callable|false $processPropertyDocBlock
*
* @return $this
*/
public function processPropertyDocBlock($processPropertyDocBlock)
{
$this->processPropertyDocBlock = $processPropertyDocBlock;
return $this;
}
/**
* @param callable $reorder
*
* @return $this
*/
public function reorder($reorder)
{
$this->reorder = $reorder;
return $this;
}
/**
* @param callable $reorderMethods
*
* @return $this
*/
public function reorderMethods($reorderMethods)
{
$this->reorderMethods = $reorderMethods;
return $this;
}
/**
* @param callable $reorderProperties
*
* @return $this
*/
public function reorderProperties($reorderProperties)
{
$this->reorderProperties = $reorderProperties;
return $this;
}
/**
* @param string $filename
*
* @return $this
*/
public function filename($filename)
{
$this->filename = $filename;
return $this;
}
/**
* @param string $prepend
*
* @return $this
*/
public function prepend($prepend)
{
$this->prepend = $prepend;
return $this;
}
/**
* @param string $append
*
* @return $this
*/
public function append($append)
{
$this->append = $append;
return $this;
}
/**
* @param string $text
*
* @return $this
*/
public function text($text)
{
$this->text = $text;
return $this;
}
/**
* @param string $item
*
* @return $this
*/
public function textForClass($item)
{
$this->textForClass[] = $item;
return $this;
}
/**
* {@inheritdoc}
*/
public function run()
{
foreach ($this->docClass as $class) {
$this->printTaskInfo("Processing {class}", ['class' => $class]);
$this->textForClass[$class] = $this->documentClass($class);
}
if (is_callable($this->reorder)) {
$this->printTaskInfo("Applying reorder function");
call_user_func_array($this->reorder, [$this->textForClass]);
}
$this->text = implode("\n", $this->textForClass);
/** @var \Robo\Result $result */
$result = $this->collectionBuilder()->taskWriteToFile($this->filename)
->line($this->prepend)
->text($this->text)
->line($this->append)
->run();
$this->printTaskSuccess('{filename} created. {class-count} classes documented', ['filename' => $this->filename, 'class-count' => count($this->docClass)]);
return new Result($this, $result->getExitCode(), $result->getMessage(), $this->textForClass);
}
/**
* @param string $class
*
* @return null|string
*/
protected function documentClass($class)
{
if (!class_exists($class)) {
return "";
}
$refl = new \ReflectionClass($class);
if (is_callable($this->filterClasses)) {
$ret = call_user_func($this->filterClasses, $refl);
if (!$ret) {
return;
}
}
$doc = $this->documentClassSignature($refl);
$doc .= "\n" . $this->documentClassDocBlock($refl);
$doc .= "\n";
if (is_callable($this->processClass)) {
$doc = call_user_func($this->processClass, $refl, $doc);
}
$properties = [];
foreach ($refl->getProperties() as $reflProperty) {
$properties[] = $this->documentProperty($reflProperty);
}
$properties = array_filter($properties);
$doc .= implode("\n", $properties);
$methods = [];
foreach ($refl->getMethods() as $reflMethod) {
$methods[$reflMethod->name] = $this->documentMethod($reflMethod);
}
if (is_callable($this->reorderMethods)) {
call_user_func_array($this->reorderMethods, [&$methods]);
}
$methods = array_filter($methods);
$doc .= implode("\n", $methods)."\n";
return $doc;
}
/**
* @param \ReflectionClass $reflectionClass
*
* @return string
*/
protected function documentClassSignature(\ReflectionClass $reflectionClass)
{
if ($this->processClassSignature === false) {
return "";
}
$signature = "## {$reflectionClass->name}\n\n";
if ($parent = $reflectionClass->getParentClass()) {
$signature .= "* *Extends* `{$parent->name}`";
}
$interfaces = $reflectionClass->getInterfaceNames();
if (count($interfaces)) {
$signature .= "\n* *Implements* `" . implode('`, `', $interfaces) . '`';
}
$traits = $reflectionClass->getTraitNames();
if (count($traits)) {
$signature .= "\n* *Uses* `" . implode('`, `', $traits) . '`';
}
if (is_callable($this->processClassSignature)) {
$signature = call_user_func($this->processClassSignature, $reflectionClass, $signature);
}
return $signature;
}
/**
* @param \ReflectionClass $reflectionClass
*
* @return string
*/
protected function documentClassDocBlock(\ReflectionClass $reflectionClass)
{
if ($this->processClassDocBlock === false) {
return "";
}
$doc = self::indentDoc($reflectionClass->getDocComment());
if (is_callable($this->processClassDocBlock)) {
$doc = call_user_func($this->processClassDocBlock, $reflectionClass, $doc);
}
return $doc;
}
/**
* @param \ReflectionMethod $reflectedMethod
*
* @return string
*/
protected function documentMethod(\ReflectionMethod $reflectedMethod)
{
if ($this->processMethod === false) {
return "";
}
if (is_callable($this->filterMethods)) {
$ret = call_user_func($this->filterMethods, $reflectedMethod);
if (!$ret) {
return "";
}
} else {
if (!$reflectedMethod->isPublic()) {
return "";
}
}
$signature = $this->documentMethodSignature($reflectedMethod);
$docblock = $this->documentMethodDocBlock($reflectedMethod);
$methodDoc = "$signature $docblock";
if (is_callable($this->processMethod)) {
$methodDoc = call_user_func($this->processMethod, $reflectedMethod, $methodDoc);
}
return $methodDoc;
}
/**
* @param \ReflectionProperty $reflectedProperty
*
* @return string
*/
protected function documentProperty(\ReflectionProperty $reflectedProperty)
{
if ($this->processProperty === false) {
return "";
}
if (is_callable($this->filterProperties)) {
$ret = call_user_func($this->filterProperties, $reflectedProperty);
if (!$ret) {
return "";
}
} else {
if (!$reflectedProperty->isPublic()) {
return "";
}
}
$signature = $this->documentPropertySignature($reflectedProperty);
$docblock = $this->documentPropertyDocBlock($reflectedProperty);
$propertyDoc = $signature . $docblock;
if (is_callable($this->processProperty)) {
$propertyDoc = call_user_func($this->processProperty, $reflectedProperty, $propertyDoc);
}
return $propertyDoc;
}
/**
* @param \ReflectionProperty $reflectedProperty
*
* @return string
*/
protected function documentPropertySignature(\ReflectionProperty $reflectedProperty)
{
if ($this->processPropertySignature === false) {
return "";
}
$modifiers = implode(' ', \Reflection::getModifierNames($reflectedProperty->getModifiers()));
$signature = "#### *$modifiers* {$reflectedProperty->name}";
if (is_callable($this->processPropertySignature)) {
$signature = call_user_func($this->processPropertySignature, $reflectedProperty, $signature);
}
return $signature;
}
/**
* @param \ReflectionProperty $reflectedProperty
*
* @return string
*/
protected function documentPropertyDocBlock(\ReflectionProperty $reflectedProperty)
{
if ($this->processPropertyDocBlock === false) {
return "";
}
$propertyDoc = $reflectedProperty->getDocComment();
// take from parent
if (!$propertyDoc) {
$parent = $reflectedProperty->getDeclaringClass();
while ($parent = $parent->getParentClass()) {
if ($parent->hasProperty($reflectedProperty->name)) {
$propertyDoc = $parent->getProperty($reflectedProperty->name)->getDocComment();
}
}
}
$propertyDoc = self::indentDoc($propertyDoc, 7);
$propertyDoc = preg_replace("~^@(.*?)([$\s])~", ' * `$1` $2', $propertyDoc); // format annotations
if (is_callable($this->processPropertyDocBlock)) {
$propertyDoc = call_user_func($this->processPropertyDocBlock, $reflectedProperty, $propertyDoc);
}
return ltrim($propertyDoc);
}
/**
* @param \ReflectionParameter $param
*
* @return string
*/
protected function documentParam(\ReflectionParameter $param)
{
$text = "";
if ($param->isArray()) {
$text .= 'array ';
}
if ($param->isCallable()) {
$text .= 'callable ';
}
$text .= '$' . $param->name;
if ($param->isDefaultValueAvailable()) {
if ($param->allowsNull()) {
$text .= ' = null';
} else {
$text .= ' = ' . str_replace("\n", ' ', print_r($param->getDefaultValue(), true));
}
}
return $text;
}
/**
* @param string $doc
* @param int $indent
*
* @return string
*/
public static function indentDoc($doc, $indent = 3)
{
if (!$doc) {
return $doc;
}
return implode(
"\n",
array_map(
function ($line) use ($indent) {
return substr($line, $indent);
},
explode("\n", $doc)
)
);
}
/**
* @param \ReflectionMethod $reflectedMethod
*
* @return string
*/
protected function documentMethodSignature(\ReflectionMethod $reflectedMethod)
{
if ($this->processMethodSignature === false) {
return "";
}
$modifiers = implode(' ', \Reflection::getModifierNames($reflectedMethod->getModifiers()));
$params = implode(
', ',
array_map(
function ($p) {
return $this->documentParam($p);
},
$reflectedMethod->getParameters()
)
);
$signature = "#### *$modifiers* {$reflectedMethod->name}($params)";
if (is_callable($this->processMethodSignature)) {
$signature = call_user_func($this->processMethodSignature, $reflectedMethod, $signature);
}
return $signature;
}
/**
* @param \ReflectionMethod $reflectedMethod
*
* @return string
*/
protected function documentMethodDocBlock(\ReflectionMethod $reflectedMethod)
{
if ($this->processMethodDocBlock === false) {
return "";
}
$methodDoc = $reflectedMethod->getDocComment();
// take from parent
if (!$methodDoc) {
$parent = $reflectedMethod->getDeclaringClass();
while ($parent = $parent->getParentClass()) {
if ($parent->hasMethod($reflectedMethod->name)) {
$methodDoc = $parent->getMethod($reflectedMethod->name)->getDocComment();
}
}
}
// take from interface
if (!$methodDoc) {
$interfaces = $reflectedMethod->getDeclaringClass()->getInterfaces();
foreach ($interfaces as $interface) {
$i = new \ReflectionClass($interface->name);
if ($i->hasMethod($reflectedMethod->name)) {
$methodDoc = $i->getMethod($reflectedMethod->name)->getDocComment();
break;
}
}
}
$methodDoc = self::indentDoc($methodDoc, 7);
$methodDoc = preg_replace("~^@(.*?) ([$\s])~m", ' * `$1` $2', $methodDoc); // format annotations
if (is_callable($this->processMethodDocBlock)) {
$methodDoc = call_user_func($this->processMethodDocBlock, $reflectedMethod, $methodDoc);
}
return $methodDoc;
}
}
<?php
namespace Robo\Task\Development;
use Robo\Task\BaseTask;
use Symfony\Component\Process\ProcessUtils;
use Robo\Result;
/**
* Generate a Robo Task that is a wrapper around an existing class.
*
* ``` php
* <?php
* $this->taskGenerateTask('Symfony\Component\Filesystem\Filesystem', 'FilesystemStack')
* ->run();
* ```
*/
class GenerateTask extends BaseTask
{
/**
* @var string
*/
protected $className;
/**
* @var string
*/
protected $wrapperClassName;
/**
* @param string $className
* @param string $wrapperClassName
*/
public function __construct($className, $wrapperClassName = '')
{
$this->className = $className;
$this->wrapperClassName = $wrapperClassName;
}
/**
* {@inheritdoc}
*/
public function run()
{
$delegate = new \ReflectionClass($this->className);
$replacements = [];
$leadingCommentChars = " * ";
$methodDescriptions = [];
$methodImplementations = [];
$immediateMethods = [];
foreach ($delegate->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
$methodName = $method->name;
$getter = preg_match('/^(get|has|is)/', $methodName);
$setter = preg_match('/^(set|unset)/', $methodName);
$argPrototypeList = [];
$argNameList = [];
$needsImplementation = false;
foreach ($method->getParameters() as $arg) {
$argDescription = '$' . $arg->name;
$argNameList[] = $argDescription;
if ($arg->isOptional()) {
$argDescription = $argDescription . ' = ' . str_replace("\n", "", var_export($arg->getDefaultValue(), true));
// We will create wrapper methods for any method that
// has default parameters.
$needsImplementation = true;
}
$argPrototypeList[] = $argDescription;
}
$argPrototypeString = implode(', ', $argPrototypeList);
$argNameListString = implode(', ', $argNameList);
if ($methodName[0] != '_') {
$methodDescriptions[] = "@method $methodName($argPrototypeString)";
if ($getter) {
$immediateMethods[] = " public function $methodName($argPrototypeString)\n {\n return \$this->delegate->$methodName($argNameListString);\n }";
} elseif ($setter) {
$immediateMethods[] = " public function $methodName($argPrototypeString)\n {\n \$this->delegate->$methodName($argNameListString);\n return \$this;\n }";
} elseif ($needsImplementation) {
// Include an implementation for the wrapper method if necessary
$methodImplementations[] = " protected function _$methodName($argPrototypeString)\n {\n \$this->delegate->$methodName($argNameListString);\n }";
}
}
}
$classNameParts = explode('\\', $this->className);
$delegate = array_pop($classNameParts);
$delegateNamespace = implode('\\', $classNameParts);
if (empty($this->wrapperClassName)) {
$this->wrapperClassName = $delegate;
}
$replacements['{delegateNamespace}'] = $delegateNamespace;
$replacements['{delegate}'] = $delegate;
$replacements['{wrapperClassName}'] = $this->wrapperClassName;
$replacements['{taskname}'] = "task$delegate";
$replacements['{methodList}'] = $leadingCommentChars . implode("\n$leadingCommentChars", $methodDescriptions);
$replacements['{immediateMethods}'] = "\n\n" . implode("\n\n", $immediateMethods);
$replacements['{methodImplementations}'] = "\n\n" . implode("\n\n", $methodImplementations);
$template = file_get_contents(__DIR__ . '/../../../data/Task/Development/GeneratedWrapper.tmpl');
$template = str_replace(array_keys($replacements), array_values($replacements), $template);
// Returning data in the $message will cause it to be printed.
return Result::success($this, $template);
}
}
<?php
namespace Robo\Task\Development;
use Robo\Exception\TaskException;
use Robo\Task\BaseTask;
/**
* @method \Robo\Task\Development\GitHub repo(string)
* @method \Robo\Task\Development\GitHub owner(string)
*/
abstract class GitHub extends BaseTask
{
const GITHUB_URL = 'https://api.github.com';
/**
* @var string
*/
protected $user = '';
/**
* @var string
*/
protected $password = '';
/**
* @var string
*/
protected $repo;
/**
* @var string
*/
protected $owner;
/**
* @param string $repo
*
* @return $this
*/
public function repo($repo)
{
$this->repo = $repo;
return $this;
}
/**
* @param string $owner
*
* @return $this
*/
public function owner($owner)
{
$this->owner = $owner;
return $this;
}
/**
* @param string $uri
*
* @return $this
*/
public function uri($uri)
{
list($this->owner, $this->repo) = explode('/', $uri);
return $this;
}
/**
* @return string
*/
protected function getUri()
{
return $this->owner . '/' . $this->repo;
}
/**
* @param string $user
*
* @return $this
*/
public function user($user)
{
$this->user = $user;
return $this;
}
/**
* @param $password
*
* @return $this
*/
public function password($password)
{
$this->password = $password;
return $this;
}
/**
* @param string $uri
* @param array $params
* @param string $method
*
* @return array
*
* @throws \Robo\Exception\TaskException
*/
protected function sendRequest($uri, $params = [], $method = 'POST')
{
if (!$this->owner or !$this->repo) {
throw new TaskException($this, 'Repo URI is not set');
}
$ch = curl_init();
$url = sprintf('%s/repos/%s/%s', self::GITHUB_URL, $this->getUri(), $uri);
$this->printTaskInfo($url);
$this->printTaskInfo('{method} {url}', ['method' => $method, 'url' => $url]);
if (!empty($this->user)) {
curl_setopt($ch, CURLOPT_USERPWD, $this->user . ':' . $this->password);
}
curl_setopt_array(
$ch,
array(
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => $method != 'GET',
CURLOPT_POSTFIELDS => json_encode($params),
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_USERAGENT => "Robo"
)
);
$output = curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$response = json_decode($output);
$this->printTaskInfo($output);
return [$code, $response];
}
}
<?php
namespace Robo\Task\Development;
use Robo\Result;
/**
* Publishes new GitHub release.
*
* ``` php
* <?php
* $this->taskGitHubRelease('0.1.0')
* ->uri('consolidation-org/Robo')
* ->description('Add stuff people need.')
* ->change('Fix #123')
* ->change('Add frobulation method to all widgets')
* ->run();
* ?>
* ```
*/
class GitHubRelease extends GitHub
{
/**
* @var string
*/
protected $tag;
/**
* @var string
*/
protected $name;
/**
* @var string
*/
protected $description = '';
/**
* @var string[]
*/
protected $changes = [];
/**
* @var bool
*/
protected $draft = false;
/**
* @var bool
*/
protected $prerelease = false;
/**
* @var string
*/
protected $comittish = 'master';
/**
* @param string $tag
*/
public function __construct($tag)
{
$this->tag = $tag;
}
/**
* @param string $tag
*
* @return $this
*/
public function tag($tag)
{
$this->tag = $tag;
return $this;
}
/**
* @param bool $draft
*
* @return $this
*/
public function draft($draft)
{
$this->draft = $draft;
return $this;
}
/**
* @param string $name
*
* @return $this
*/
public function name($name)
{
$this->name = $name;
return $this;
}
/**
* @param string $description
*
* @return $this
*/
public function description($description)
{
$this->description = $description;
return $this;
}
/**
* @param bool $prerelease
*
* @return $this
*/
public function prerelease($prerelease)
{
$this->prerelease = $prerelease;
return $this;
}
/**
* @param string $comittish
*
* @return $this
*/
public function comittish($comittish)
{
$this->comittish = $comittish;
return $this;
}
/**
* @param string $description
*
* @return $this
*/
public function appendDescription($description)
{
if (!empty($this->description)) {
$this->description .= "\n\n";
}
$this->description .= $description;
return $this;
}
public function changes(array $changes)
{
$this->changes = array_merge($this->changes, $changes);
return $this;
}
/**
* @param string $change
*
* @return $this
*/
public function change($change)
{
$this->changes[] = $change;
return $this;
}
/**
* @return string
*/
protected function getBody()
{
$body = $this->description;
if (!empty($this->changes)) {
$changes = array_map(
function ($line) {
return "* $line";
},
$this->changes
);
$changesText = implode("\n", $changes);
$body .= "### Changelog \n\n$changesText";
}
return $body;
}
/**
* {@inheritdoc}
*/
public function run()
{
$this->printTaskInfo('Releasing {tag}', ['tag' => $this->tag]);
$this->startTimer();
list($code, $data) = $this->sendRequest(
'releases',
[
"tag_name" => $this->tag,
"target_commitish" => $this->comittish,
"name" => $this->tag,
"body" => $this->getBody(),
"draft" => $this->draft,
"prerelease" => $this->prerelease
]
);
$this->stopTimer();
return new Result(
$this,
in_array($code, [200, 201]) ? 0 : 1,
isset($data->message) ? $data->message : '',
['response' => $data, 'time' => $this->getExecutionTime()]
);
}
}
<?php
namespace Robo\Task\Development;
trait loadTasks
{
/**
* @param string $filename
*
* @return Changelog
*/
protected function taskChangelog($filename = 'CHANGELOG.md')
{
return $this->task(Changelog::class, $filename);
}
/**
* @param string $filename
*
* @return GenerateMarkdownDoc
*/
protected function taskGenDoc($filename)
{
return $this->task(GenerateMarkdownDoc::class, $filename);
}
/**
* @param string $className
* @param string $wrapperClassName
*
* @return \Robo\Task\Development\GenerateTask
*/
protected function taskGenTask($className, $wrapperClassName = '')
{
return $this->task(GenerateTask::class, $className, $wrapperClassName);
}
/**
* @param string $pathToSemVer
*
* @return SemVer
*/
protected function taskSemVer($pathToSemVer = '.semver')
{
return $this->task(SemVer::class, $pathToSemVer);
}
/**
* @param int $port
*
* @return PhpServer
*/
protected function taskServer($port = 8000)
{
return $this->task(PhpServer::class, $port);
}
/**
* @param string $filename
*
* @return PackPhar
*/
protected function taskPackPhar($filename)
{
return $this->task(PackPhar::class, $filename);
}
/**
* @param string $tag
*
* @return GitHubRelease
*/
protected function taskGitHubRelease($tag)
{
return $this->task(GitHubRelease::class, $tag);
}
/**
* @param string|array $url
*
* @return OpenBrowser
*/
protected function taskOpenBrowser($url)
{
return $this->task(OpenBrowser::class, $url);
}
}
<?php
namespace Robo\Task\Development;
use Robo\Task\BaseTask;
use Symfony\Component\Process\ProcessUtils;
use Robo\Result;
/**
* Opens the default's user browser
* code inspired from openBrowser() function in https://github.com/composer/composer/blob/master/src/Composer/Command/HomeCommand.php
*
* ``` php
* <?php
* // open one browser window
* $this->taskOpenBrowser('http://localhost')
* ->run();
*
* // open two browser windows
* $this->taskOpenBrowser([
* 'http://localhost/mysite',
* 'http://localhost/mysite2'
* ])
* ->run();
* ```
*/
class OpenBrowser extends BaseTask
{
/**
* @var string[]
*/
protected $urls = [];
/**
* @param string|array $url
*/
public function __construct($url)
{
$this->urls = (array) $url;
}
/**
* {@inheritdoc}
*/
public function run()
{
$openCommand = $this->getOpenCommand();
if (empty($openCommand)) {
return Result::error($this, 'no suitable browser opening command found');
}
foreach ($this->urls as $url) {
passthru(sprintf($openCommand, ProcessUtils::escapeArgument($url)));
$this->printTaskInfo('Opened {url}', ['url' => $url]);
}
return Result::success($this);
}
/**
* @return null|string
*/
private function getOpenCommand()
{
if (defined('PHP_WINDOWS_VERSION_MAJOR')) {
return 'start "web" explorer "%s"';
}
passthru('which xdg-open', $linux);
passthru('which open', $osx);
if (0 === $linux) {
return 'xdg-open %s';
}
if (0 === $osx) {
return 'open %s';
}
}
}
<?php
namespace Robo\Task\Development;
use Robo\Contract\ProgressIndicatorAwareInterface;
use Robo\Common\ProgressIndicatorAwareTrait;
use Robo\Contract\PrintedInterface;
use Robo\Result;
use Robo\Task\BaseTask;
/**
* Creates Phar.
*
* ``` php
* <?php
* $pharTask = $this->taskPackPhar('package/codecept.phar')
* ->compress()
* ->stub('package/stub.php');
*
* $finder = Finder::create()
* ->name('*.php')
* ->in('src');
*
* foreach ($finder as $file) {
* $pharTask->addFile('src/'.$file->getRelativePathname(), $file->getRealPath());
* }
*
* $finder = Finder::create()->files()
* ->name('*.php')
* ->in('vendor');
*
* foreach ($finder as $file) {
* $pharTask->addStripped('vendor/'.$file->getRelativePathname(), $file->getRealPath());
* }
* $pharTask->run();
*
* // verify Phar is packed correctly
* $code = $this->_exec('php package/codecept.phar');
* ?>
* ```
*/
class PackPhar extends BaseTask implements PrintedInterface, ProgressIndicatorAwareInterface
{
/**
* @var \Phar
*/
protected $phar;
/**
* @var null|string
*/
protected $compileDir = null;
/**
* @var string
*/
protected $filename;
/**
* @var bool
*/
protected $compress = false;
protected $stub;
protected $bin;
/**
* @var string
*/
protected $stubTemplate = <<<EOF
#!/usr/bin/env php
<?php
Phar::mapPhar();
%s
__HALT_COMPILER();
EOF;
/**
* @var string[]
*/
protected $files = [];
/**
* {@inheritdoc}
*/
public function getPrinted()
{
return true;
}
/**
* @param string $filename
*/
public function __construct($filename)
{
$file = new \SplFileInfo($filename);
$this->filename = $filename;
if (file_exists($file->getRealPath())) {
@unlink($file->getRealPath());
}
$this->phar = new \Phar($file->getPathname(), 0, $file->getFilename());
}
/**
* @param bool $compress
*
* @return $this
*/
public function compress($compress = true)
{
$this->compress = $compress;
return $this;
}
/**
* @param string $stub
*
* @return $this
*/
public function stub($stub)
{
$this->phar->setStub(file_get_contents($stub));
return $this;
}
/**
* {@inheritdoc}
*/
public function progressIndicatorSteps()
{
// run() will call advanceProgressIndicator() once for each
// file, one after calling stopBuffering, and again after compression.
return count($this->files)+2;
}
/**
* {@inheritdoc}
*/
public function run()
{
$this->printTaskInfo('Creating {filename}', ['filename' => $this->filename]);
$this->phar->setSignatureAlgorithm(\Phar::SHA1);
$this->phar->startBuffering();
$this->printTaskInfo('Packing {file-count} files into phar', ['file-count' => count($this->files)]);
$this->startProgressIndicator();
foreach ($this->files as $path => $content) {
$this->phar->addFromString($path, $content);
$this->advanceProgressIndicator();
}
$this->phar->stopBuffering();
$this->advanceProgressIndicator();
if ($this->compress and in_array('GZ', \Phar::getSupportedCompression())) {
if (count($this->files) > 1000) {
$this->printTaskInfo('Too many files. Compression DISABLED');
} else {
$this->printTaskInfo('{filename} compressed', ['filename' => $this->filename]);
$this->phar = $this->phar->compressFiles(\Phar::GZ);
}
}
$this->advanceProgressIndicator();
$this->stopProgressIndicator();
$this->printTaskSuccess('{filename} produced', ['filename' => $this->filename]);
return Result::success($this, '', ['time' => $this->getExecutionTime()]);
}
/**
* @param string $path
* @param string $file
*
* @return $this
*/
public function addStripped($path, $file)
{
$this->files[$path] = $this->stripWhitespace(file_get_contents($file));
return $this;
}
/**
* @param string $path
* @param string $file
*
* @return $this
*/
public function addFile($path, $file)
{
$this->files[$path] = file_get_contents($file);
return $this;
}
/**
* @param \Symfony\Component\Finder\SplFileInfo[] $files
*/
public function addFiles($files)
{
foreach ($files as $file) {
$this->addFile($file->getRelativePathname(), $file->getRealPath());
}
}
/**
* @param string $file
*
* @return $this
*/
public function executable($file)
{
$source = file_get_contents($file);
if (strpos($source, '#!/usr/bin/env php') === 0) {
$source = substr($source, strpos($source, '<?php') + 5);
}
$this->phar->setStub(sprintf($this->stubTemplate, $source));
return $this;
}
/**
* Strips whitespace from source. Taken from composer
*
* @param string $source
*
* @return string
*/
private function stripWhitespace($source)
{
if (!function_exists('token_get_all')) {
return $source;
}
$output = '';
foreach (token_get_all($source) as $token) {
if (is_string($token)) {
$output .= $token;
} elseif (in_array($token[0], array(T_COMMENT, T_DOC_COMMENT))) {
// $output .= $token[1];
$output .= str_repeat("\n", substr_count($token[1], "\n"));
} elseif (T_WHITESPACE === $token[0]) {
// reduce wide spaces
$whitespace = preg_replace('{[ \t]+}', ' ', $token[1]);
// normalize newlines to \n
$whitespace = preg_replace('{(?:\r\n|\r|\n)}', "\n", $whitespace);
// trim leading spaces
$whitespace = preg_replace('{\n +}', "\n", $whitespace);
$output .= $whitespace;
} else {
$output .= $token[1];
}
}
return $output;
}
}
<?php
namespace Robo\Task\Development;
use Robo\Task\Base\Exec;
/**
* Runs PHP server and stops it when task finishes.
*
* ``` php
* <?php
* // run server in /public directory
* $this->taskServer(8000)
* ->dir('public')
* ->run();
*
* // run with IP 0.0.0.0
* $this->taskServer(8000)
* ->host('0.0.0.0')
* ->run();
*
* // execute server in background
* $this->taskServer(8000)
* ->background()
* ->run();
* ?>
* ```
*/
class PhpServer extends Exec
{
/**
* @var int
*/
protected $port;
/**
* @var string
*/
protected $host = '127.0.0.1';
/**
* {@inheritdoc}
*/
protected $command = 'php -S %s:%d ';
/**
* @param int $port
*/
public function __construct($port)
{
$this->port = $port;
if (strtolower(PHP_OS) === 'linux') {
$this->command = 'exec php -S %s:%d ';
}
}
/**
* @param string $host
*
* @return $this
*/
public function host($host)
{
$this->host = $host;
return $this;
}
/**
* @param string $path
*
* @return $this
*/
public function dir($path)
{
$this->command .= "-t $path";
return $this;
}
/**
* {@inheritdoc}
*/
public function getCommand()
{
return sprintf($this->command . $this->arguments, $this->host, $this->port);
}
}
<?php
namespace Robo\Task\Development;
use Robo\Result;
use Robo\Contract\TaskInterface;
use Robo\Exception\TaskException;
/**
* Helps to maintain `.semver` file.
*
* ```php
* <?php
* $this->taskSemVer('.semver')
* ->increment()
* ->run();
* ?>
* ```
*
*/
class SemVer implements TaskInterface
{
const SEMVER = "---\n:major: %d\n:minor: %d\n:patch: %d\n:special: '%s'\n:metadata: '%s'";
const REGEX = "/^\-\-\-\n:major:\s(0|[1-9]\d*)\n:minor:\s(0|[1-9]\d*)\n:patch:\s(0|[1-9]\d*)\n:special:\s'([a-zA-z0-9]*\.?(?:0|[1-9]\d*)?)'\n:metadata:\s'((?:0|[1-9]\d*)?(?:\.[a-zA-z0-9\.]*)?)'/";
/**
* @var string
*/
protected $format = 'v%M.%m.%p%s';
/**
* @var string
*/
protected $specialSeparator = '-';
/**
* @var string
*/
protected $metadataSeparator = '+';
/**
* @var string
*/
protected $path;
/**
* @var array
*/
protected $version = [
'major' => 0,
'minor' => 0,
'patch' => 0,
'special' => '',
'metadata' => ''
];
/**
* @param string $filename
*/
public function __construct($filename = '')
{
$this->path = $filename;
if (file_exists($this->path)) {
$this->parse();
}
}
/**
* @return string
*/
public function __toString()
{
$search = ['%M', '%m', '%p', '%s'];
$replace = $this->version + ['extra' => ''];
foreach (['special', 'metadata'] as $key) {
if (!empty($replace[$key])) {
$separator = $key . 'Separator';
$replace['extra'] .= $this->{$separator} . $replace[$key];
}
unset($replace[$key]);
}
return str_replace($search, $replace, $this->format);
}
/**
* @param string $format
*
* @return $this
*/
public function setFormat($format)
{
$this->format = $format;
return $this;
}
/**
* @param string $separator
*
* @return $this
*/
public function setMetadataSeparator($separator)
{
$this->metadataSeparator = $separator;
return $this;
}
/**
* @param string $separator
*
* @return $this
*/
public function setPrereleaseSeparator($separator)
{
$this->specialSeparator = $separator;
return $this;
}
/**
* @param string $what
*
* @return $this
*
* @throws \Robo\Exception\TaskException
*/
public function increment($what = 'patch')
{
switch ($what) {
case 'major':
$this->version['major']++;
$this->version['minor'] = 0;
$this->version['patch'] = 0;
break;
case 'minor':
$this->version['minor']++;
$this->version['patch'] = 0;
break;
case 'patch':
$this->version['patch']++;
break;
default:
throw new TaskException(
$this,
'Bad argument, only one of the following is allowed: major, minor, patch'
);
}
return $this;
}
/**
* @param string $tag
*
* @return $this
*
* @throws \Robo\Exception\TaskException
*/
public function prerelease($tag = 'RC')
{
if (!is_string($tag)) {
throw new TaskException($this, 'Bad argument, only strings allowed.');
}
$number = 0;
if (!empty($this->version['special'])) {
list($current, $number) = explode('.', $this->version['special']);
if ($tag != $current) {
$number = 0;
}
}
$number++;
$this->version['special'] = implode('.', [$tag, $number]);
return $this;
}
/**
* @param array|string $data
*
* @return $this
*/
public function metadata($data)
{
if (is_array($data)) {
$data = implode('.', $data);
}
$this->version['metadata'] = $data;
return $this;
}
/**
* {@inheritdoc}
*/
public function run()
{
$written = $this->dump();
return new Result($this, (int)($written === false), $this->__toString());
}
/**
* @return bool
*
* @throws \Robo\Exception\TaskException
*/
protected function dump()
{
extract($this->version);
$semver = sprintf(self::SEMVER, $major, $minor, $patch, $special, $metadata);
if (is_writeable($this->path) === false || file_put_contents($this->path, $semver) === false) {
throw new TaskException($this, 'Failed to write semver file.');
}
return true;
}
/**
* @throws \Robo\Exception\TaskException
*/
protected function parse()
{
$output = file_get_contents($this->path);
if (!preg_match_all(self::REGEX, $output, $matches)) {
throw new TaskException($this, 'Bad semver file.');
}
list(, $major, $minor, $patch, $special, $metadata) = array_map('current', $matches);
$this->version = compact('major', 'minor', 'patch', 'special', 'metadata');
}
}
<?php
namespace Robo\Task\Docker;
use Robo\Common\ExecOneCommand;
use Robo\Contract\PrintedInterface;
use Robo\Task\BaseTask;
abstract class Base extends BaseTask implements PrintedInterface
{
use ExecOneCommand;
/**
* @var string
*/
protected $command = '';
/**
* {@inheritdoc}
*/
public function run()
{
$command = $this->getCommand();
$this->printTaskInfo('Running {command}', ['command' => $command]);
return $this->executeCommand($command);
}
abstract public function getCommand();
}
<?php
namespace Robo\Task\Docker;
/**
* Builds Docker image
*
* ```php
* <?php
* $this->taskDockerBuild()->run();
*
* $this->taskDockerBuild('path/to/dir')
* ->tag('database')
* ->run();
*
* ?>
*
* ```
*
* Class Build
* @package Robo\Task\Docker
*/
class Build extends Base
{
/**
* @var string
*/
protected $path;
/**
* @param string $path
*/
public function __construct($path = '.')
{
$this->command = "docker build";
$this->path = $path;
}
/**
* {@inheritdoc}
*/
public function getCommand()
{
return $this->command . ' ' . $this->arguments . ' ' . $this->path;
}
/**
* @param string $tag
*
* @return $this
*/
public function tag($tag)
{
return $this->option('-t', $tag);
}
}
<?php
namespace Robo\Task\Docker;
/**
* Commits docker container to an image
*
* ```
* $this->taskDockerCommit($containerId)
* ->name('my/database')
* ->run();
*
* // alternatively you can take the result from DockerRun task:
*
* $result = $this->taskDockerRun('db')
* ->exec('./prepare_database.sh')
* ->run();
*
* $task->dockerCommit($result)
* ->name('my/database')
* ->run();
* ```
*/
class Commit extends Base
{
/**
* @var string
*/
protected $command = "docker commit";
/**
* @var string
*/
protected $name;
/**
* @var string
*/
protected $cid;
/**
* @param string|\Robo\Task\Docker\Result $cidOrResult
*/
public function __construct($cidOrResult)
{
$this->cid = $cidOrResult instanceof Result ? $cidOrResult->getCid() : $cidOrResult;
}
/**
* {@inheritdoc}
*/
public function getCommand()
{
return $this->command . ' ' . $this->cid . ' ' . $this->name . ' ' . $this->arguments;
}
/**
* @param $name
*
* @return $this
*/
public function name($name)
{
$this->name = $name;
return $this;
}
}
<?php
namespace Robo\Task\Docker;
use Robo\Common\CommandReceiver;
/**
* Executes command inside running Docker container
*
* ```php
* <?php
* $test = $this->taskDockerRun('test_env')
* ->detached()
* ->run();
*
* $this->taskDockerExec($test)
* ->interactive()
* ->exec('./runtests')
* ->run();
*
* // alternatively use commands from other tasks
*
* $this->taskDockerExec($test)
* ->interactive()
* ->exec($this->taskCodecept()->suite('acceptance'))
* ->run();
* ?>
* ```
*
*/
class Exec extends Base
{
use CommandReceiver;
/**
* @var string
*/
protected $command = "docker exec";
/**
* @var string
*/
protected $cid;
/**
* @var string
*/
protected $run = '';
/**
* @param string|\Robo\Result $cidOrResult
*/
public function __construct($cidOrResult)
{
$this->cid = $cidOrResult instanceof Result ? $cidOrResult->getCid() : $cidOrResult;
}
/**
* @return $this
*/
public function detached()
{
$this->option('-d');
return $this;
}
/**
* @return $this
*/
public function interactive()
{
$this->option('-i');
return $this;
}
/**
* @param string|\Robo\Contract\CommandInterface $command
*
* @return $this
*/
public function exec($command)
{
$this->run = $this->receiveCommand($command);
return $this;
}
/**
* {@inheritdoc}
*/
public function getCommand()
{
return $this->command . ' ' . $this->arguments . ' ' . $this->cid.' '.$this->run;
}
}
<?php
namespace Robo\Task\Docker;
trait loadTasks
{
/**
* @param string $image
*
* @return \Robo\Task\Docker\Run
*/
protected function taskDockerRun($image)
{
return $this->task(Run::class, $image);
}
/**
* @param string $image
*
* @return \Robo\Task\Docker\Pull
*/
protected function taskDockerPull($image)
{
return $this->task(Pull::class, $image);
}
/**
* @param string $path
*
* @return \Robo\Task\Docker\Build
*/
protected function taskDockerBuild($path = '.')
{
return $this->task(Build::class, $path);
}
/**
* @param string|\Robo\Task\Docker\Result $cidOrResult
*
* @return \Robo\Task\Docker\Stop
*/
protected function taskDockerStop($cidOrResult)
{
return $this->task(Stop::class, $cidOrResult);
}
/**
* @param string|\Robo\Task\Docker\Result $cidOrResult
*
* @return \Robo\Task\Docker\Commit
*/
protected function taskDockerCommit($cidOrResult)
{
return $this->task(Commit::class, $cidOrResult);
}
/**
* @param string|\Robo\Task\Docker\Result $cidOrResult
*
* @return \Robo\Task\Docker\Start
*/
protected function taskDockerStart($cidOrResult)
{
return $this->task(Start::class, $cidOrResult);
}
/**
* @param string|\Robo\Task\Docker\Result $cidOrResult
*
* @return \Robo\Task\Docker\Remove
*/
protected function taskDockerRemove($cidOrResult)
{
return $this->task(Remove::class, $cidOrResult);
}
/**
* @param string|\Robo\Task\Docker\Result $cidOrResult
*
* @return \Robo\Task\Docker\Exec
*/
protected function taskDockerExec($cidOrResult)
{
return $this->task(Exec::class, $cidOrResult);
}
}
<?php
namespace Robo\Task\Docker;
/**
* Pulls an image from DockerHub
*
* ```php
* <?php
* $this->taskDockerPull('wordpress')
* ->run();
*
* ?>
* ```
*
*/
class Pull extends Base
{
/**
* @param string $image
*/
public function __construct($image)
{
$this->command = "docker pull $image ";
}
/**
* {@inheritdoc}
*/
public function getCommand()
{
return $this->command . ' ' . $this->arguments;
}
}
<?php
namespace Robo\Task\Docker;
/**
* Remove docker container
*
* ```php
* <?php
* $this->taskDockerRemove($container)
* ->run();
* ?>
* ```
*
*/
class Remove extends Base
{
/**
* @param string $container
*/
public function __construct($container)
{
$this->command = "docker rm $container ";
}
/**
* {@inheritdoc}
*/
public function getCommand()
{
return $this->command . ' ' . $this->arguments;
}
}
<?php
namespace Robo\Task\Docker;
class Result extends \Robo\Result
{
/**
* Do not print result, as it was already printed
*/
protected function printResult()
{
}
/**
* @return null|string
*/
public function getCid()
{
if (isset($this['cid'])) {
return $this['cid'];
}
return null;
}
/**
* @return null|string
*/
public function getContainerName()
{
if (isset($this['name'])) {
return $this['name'];
}
return null;
}
}
<?php
namespace Robo\Task\Docker;
use Robo\Common\CommandReceiver;
/**
* Performs `docker run` on a container.
*
* ```php
* <?php
* $this->taskDockerRun('mysql')->run();
*
* $result = $this->taskDockerRun('my_db_image')
* ->env('DB', 'database_name')
* ->volume('/path/to/data', '/data')
* ->detached()
* ->publish(3306)
* ->name('my_mysql')
* ->run();
*
* // retrieve container's cid:
* $this->say("Running container ".$result->getCid());
*
* // execute script inside container
* $result = $this->taskDockerRun('db')
* ->exec('prepare_test_data.sh')
* ->run();
*
* $this->taskDockerCommit($result)
* ->name('test_db')
* ->run();
*
* // link containers
* $mysql = $this->taskDockerRun('mysql')
* ->name('wp_db') // important to set name for linked container
* ->env('MYSQL_ROOT_PASSWORD', '123456')
* ->run();
*
* $this->taskDockerRun('wordpress')
* ->link($mysql)
* ->publish(80, 8080)
* ->detached()
* ->run();
*
* ?>
* ```
*
*/
class Run extends Base
{
use CommandReceiver;
/**
* @var string
*/
protected $image = '';
/**
* @var string
*/
protected $run = '';
/**
* @var string
*/
protected $cidFile;
/**
* @var string
*/
protected $name;
/**
* @var string
*/
protected $dir;
/**
* @param string $image
*/
public function __construct($image)
{
$this->image = $image;
}
/**
* {@inheritdoc}
*/
public function getPrinted()
{
return $this->isPrinted;
}
/**
* {@inheritdoc}
*/
public function getCommand()
{
if ($this->isPrinted) {
$this->option('-i');
}
if ($this->cidFile) {
$this->option('cidfile', $this->cidFile);
}
return trim('docker run ' . $this->arguments . ' ' . $this->image . ' ' . $this->run);
}
/**
* @return $this
*/
public function detached()
{
$this->option('-d');
return $this;
}
/**
* @return $this
*/
public function interactive()
{
$this->option('-i');
return $this;
}
/**
* @param string|\Robo\Contract\CommandInterface $run
*
* @return $this
*/
public function exec($run)
{
$this->run = $this->receiveCommand($run);
return $this;
}
/**
* @param string $from
* @param null|string $to
*
* @return $this
*/
public function volume($from, $to = null)
{
$volume = $to ? "$from:$to" : $from;
$this->option('-v', $volume);
return $this;
}
/**
* @param string $variable
* @param null|string $value
*
* @return $this
*/
public function env($variable, $value = null)
{
$env = $value ? "$variable=$value" : $variable;
return $this->option("-e", $env);
}
/**
* @param null|int $port
* @param null|int $portTo
*
* @return $this
*/
public function publish($port = null, $portTo = null)
{
if (!$port) {
return $this->option('-P');
}
if ($portTo) {
$port = "$port:$portTo";
}
return $this->option('-p', $port);
}
/**
* @param string $dir
*
* @return $this
*/
public function containerWorkdir($dir)
{
return $this->option('-w', $dir);
}
/**
* @param string $user
*
* @return $this
*/
public function user($user)
{
return $this->option('-u', $user);
}
/**
* @return $this
*/
public function privileged()
{
return $this->option('--privileged');
}
/**
* @param string $name
*
* @return $this
*/
public function name($name)
{
$this->name = $name;
return $this->option('name', $name);
}
/**
* @param string|\Robo\Task\Docker\Result $name
* @param string $alias
*
* @return $this
*/
public function link($name, $alias)
{
if ($name instanceof Result) {
$name = $name->getContainerName();
}
$this->option('link', "$name:$alias");
return $this;
}
/**
* @param string $dir
*
* @return $this
*/
public function tmpDir($dir)
{
$this->dir = $dir;
return $this;
}
/**
* @return string
*/
public function getTmpDir()
{
return $this->dir ? $this->dir : sys_get_temp_dir();
}
/**
* @return string
*/
public function getUniqId()
{
return uniqid();
}
/**
* {@inheritdoc}
*/
public function run()
{
$this->cidFile = $this->getTmpDir() . '/docker_' . $this->getUniqId() . '.cid';
$result = parent::run();
$result['cid'] = $this->getCid();
return $result;
}
/**
* @return null|string
*/
protected function getCid()
{
if (!$this->cidFile || !file_exists($this->cidFile)) {
return null;
}
$cid = trim(file_get_contents($this->cidFile));
@unlink($this->cidFile);
return $cid;
}
}
<?php
namespace Robo\Task\Docker;
/**
* Starts Docker container
*
* ```php
* <?php
* $this->taskDockerStart($cidOrResult)
* ->run();
* ?>
* ```
*/
class Start extends Base
{
/**
* @var string
*/
protected $command = "docker start";
/**
* @var null|string
*/
protected $cid;
/**
* @param string|\Robo\Task\Docker\Result $cidOrResult
*/
public function __construct($cidOrResult)
{
$this->cid = $cidOrResult instanceof Result ? $cidOrResult->getCid() : $cidOrResult;
}
/**
* {@inheritdoc}
*/
public function getCommand()
{
return $this->command . ' ' . $this->arguments . ' ' . $this->cid;
}
}
<?php
namespace Robo\Task\Docker;
/**
* Stops Docker container
*
* ```php
* <?php
* $this->taskDockerStop($cidOrResult)
* ->run();
* ?>
* ```
*/
class Stop extends Base
{
/**
* @var string
*/
protected $command = "docker stop";
/**
* @var null|string
*/
protected $cid;
/**
* @param string|\Robo\Task\Docker\Result $cidOrResult
*/
public function __construct($cidOrResult)
{
$this->cid = $cidOrResult instanceof Result ? $cidOrResult->getCid() : $cidOrResult;
}
/**
* {@inheritdoc}
*/
public function getCommand()
{
return $this->command . ' ' . $this->arguments . ' ' . $this->cid;
}
}
<?php
namespace Robo\Task\File;
use Iterator;
use Robo\Common\ResourceExistenceChecker;
use Robo\Result;
use Robo\Task\BaseTask;
/**
* Merges files into one. Used for preparing assets.
*
* ``` php
* <?php
* $this->taskConcat([
* 'web/assets/screen.css',
* 'web/assets/print.css',
* 'web/assets/theme.css'
* ])
* ->to('web/assets/style.css')
* ->run()
* ?>
* ```
*/
class Concat extends BaseTask
{
use ResourceExistenceChecker;
/**
* @var array|Iterator
*/
protected $files;
/**
* @var string
*/
protected $dst;
/**
* Constructor.
*
* @param array|Iterator $files
*/
public function __construct($files)
{
$this->files = $files;
}
/**
* set the destination file
*
* @param string $dst
*
* @return $this
*/
public function to($dst)
{
$this->dst = $dst;
return $this;
}
/**
* {@inheritdoc}
*/
public function run()
{
if (is_null($this->dst) || "" === $this->dst) {
return Result::error($this, 'You must specify a destination file with to() method.');
}
if (!$this->checkResources($this->files, 'file')) {
return Result::error($this, 'Source files are missing!');
}
if (file_exists($this->dst) && !is_writable($this->dst)) {
return Result::error($this, 'Destination already exists and cannot be overwritten.');
}
$dump = '';
foreach ($this->files as $path) {
foreach (glob($path) as $file) {
$dump .= file_get_contents($file) . "\n";
}
}
$this->printTaskInfo('Writing {destination}', ['destination' => $this->dst]);
$dst = $this->dst . '.part';
$write_result = file_put_contents($dst, $dump);
if (false === $write_result) {
@unlink($dst);
return Result::error($this, 'File write failed.');
}
// Cannot be cross-volume; should always succeed.
@rename($dst, $this->dst);
return Result::success($this);
}
}
<?php
namespace Robo\Task\File;
use Robo\Collection\Temporary;
trait loadTasks
{
/**
* @param array|\Iterator $files
*
* @return \Robo\Task\File\Concat
*/
protected function taskConcat($files)
{
return $this->task(Concat::class, $files);
}
/**
* @param string $file
*
* @return \Robo\Task\File\Replace
*/
protected function taskReplaceInFile($file)
{
return $this->task(Replace::class, $file);
}
/**
* @param string $file
*
* @return \Robo\Task\File\Write
*/
protected function taskWriteToFile($file)
{
return $this->task(Write::class, $file);
}
/**
* @param string $filename
* @param string $extension
* @param string $baseDir
* @param bool $includeRandomPart
*
* @return \Robo\Task\File\TmpFile
*/
protected function taskTmpFile($filename = 'tmp', $extension = '', $baseDir = '', $includeRandomPart = true)
{
return $this->task(TmpFile::class, $filename, $extension, $baseDir, $includeRandomPart);
}
}
<?php
namespace Robo\Task\File;
use Robo\Result;
use Robo\Task\BaseTask;
/**
* Performs search and replace inside a files.
*
* ``` php
* <?php
* $this->taskReplaceInFile('VERSION')
* ->from('0.2.0')
* ->to('0.3.0')
* ->run();
*
* $this->taskReplaceInFile('README.md')
* ->from(date('Y')-1)
* ->to(date('Y'))
* ->run();
*
* $this->taskReplaceInFile('config.yml')
* ->regex('~^service:~')
* ->to('services:')
* ->run();
*
* $this->taskReplaceInFile('box/robo.txt')
* ->from(array('##dbname##', '##dbhost##'))
* ->to(array('robo', 'localhost'))
* ->run();
* ?>
* ```
*
* @method regex(string) regex to match string to be replaced
* @method from(string|array) string(s) to be replaced
* @method to(string|array) value(s) to be set as a replacement
*/
class Replace extends BaseTask
{
/**
* @var string
*/
protected $filename;
/**
* @var string[]
*/
protected $from;
/**
* @var string[]
*/
protected $to;
/**
* @var string
*/
protected $regex;
/**
* @param string $filename
*/
public function __construct($filename)
{
$this->filename = $filename;
}
/**
* @param string $filename
*
* @return $this
*/
public function filename($filename)
{
$this->filename = $filename;
return $this;
}
/**
* @param string $from
*
* @return $this
*/
public function from($from)
{
$this->from = $from;
return $this;
}
/**
* @param string $to
*
* @return $this
*/
public function to($to)
{
$this->to = $to;
return $this;
}
/**
* @param string $regex
*
* @return $this
*/
public function regex($regex)
{
$this->regex = $regex;
return $this;
}
/**
* {@inheritdoc}
*/
public function run()
{
if (!file_exists($this->filename)) {
$this->printTaskError('File {filename} does not exist', ['filename' => $this->filename]);
return false;
}
$text = file_get_contents($this->filename);
if ($this->regex) {
$text = preg_replace($this->regex, $this->to, $text, -1, $count);
} else {
$text = str_replace($this->from, $this->to, $text, $count);
}
if ($count > 0) {
$res = file_put_contents($this->filename, $text);
if ($res === false) {
return Result::error($this, "Error writing to file {filename}.", ['filename' => $this->filename]);
}
$this->printTaskSuccess("{filename} updated. {count} items replaced", ['filename' => $this->filename, 'count' => $count]);
} else {
$this->printTaskInfo("{filename} unchanged. {count} items replaced", ['filename' => $this->filename, 'count' => $count]);
}
return Result::success($this, '', ['replaced' => $count]);
}
}
<?php
namespace Robo\Task\File;
use Robo\Collection\Collection;
use Robo\Contract\CompletionInterface;
/**
* Create a temporary file that is automatically cleaned up
* once the task collection is is part of completes. When created,
* it is given a random filename.
*
* This temporary file may be manipulated exacatly like taskWrite().
* It is deleted as soon as the collection it is a part of completes
* or rolls back.
*
* ``` php
* <?php
* $collection = $this->collectionBuilder();
* $tmpFilePath = $collection->taskTmpFile()
* ->line('-----')
* ->line(date('Y-m-d').' '.$title)
* ->line('----')
* ->getPath();
* $collection->run();
* ?>
* ```
*/
class TmpFile extends Write implements CompletionInterface
{
/**
* @param string $filename
* @param string $extension
* @param string $baseDir
* @param bool $includeRandomPart
*/
public function __construct($filename = 'tmp', $extension = '', $baseDir = '', $includeRandomPart = true)
{
if (empty($base)) {
$base = sys_get_temp_dir();
}
if ($includeRandomPart) {
$random = static::randomString();
$filename = "{$filename}_{$random}";
}
$filename .= $extension;
parent::__construct("{$base}/{$filename}");
}
/**
* Generate a suitably random string to use as the suffix for our
* temporary file.
*
* @param int $length
*
* @return string
*/
private static function randomString($length = 12)
{
return substr(str_shuffle('23456789abcdefghjkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ'), 0, $length);
}
/**
* Delete this file when our collection completes.
* If this temporary file is not part of a collection,
* then it will be deleted when the program terminates,
* presuming that it was created by taskTmpFile() or _tmpFile().
*/
public function complete()
{
unlink($this->getPath());
}
}
<?php
namespace Robo\Task\File;
use Robo\Result;
use Robo\Task\BaseTask;
/**
* Writes to file.
*
* ``` php
* <?php
* $this->taskWriteToFile('blogpost.md')
* ->line('-----')
* ->line(date('Y-m-d').' '.$title)
* ->line('----')
* ->run();
* ?>
* ```
*
* @method append()
*/
class Write extends BaseTask
{
/**
* @var array
*/
protected $stack = [];
/**
* @var string
*/
protected $filename;
/**
* @var bool
*/
protected $append = false;
/**
* @var null|string
*/
protected $originalContents = null;
/**
* @param string $filename
*/
public function __construct($filename)
{
$this->filename = $filename;
}
/**
* @param string $filename
*
* @return $this
*/
public function filename($filename)
{
$this->filename = $filename;
return $this;
}
/**
* @param bool $append
*
* @return $this
*/
public function append($append = true)
{
$this->append = $append;
return $this;
}
/**
* add a line.
*
* @param string $line
*
* @return $this The current instance
*/
public function line($line)
{
$this->text($line . "\n");
return $this;
}
/**
* add more lines.
*
* @param array $lines
*
* @return $this The current instance
*/
public function lines(array $lines)
{
$this->text(implode("\n", $lines) . "\n");
return $this;
}
/**
* add a text.
*
* @param string $text
*
* @return $this The current instance
*/
public function text($text)
{
$this->stack[] = array_merge([__FUNCTION__ . 'Collect'], func_get_args());
return $this;
}
/**
* add a text from a file.
*
* Note that the file is read in the run() method of this task.
* To load text from the current state of a file (e.g. one that may
* be deleted or altered by other tasks prior the execution of this one),
* use:
* $task->text(file_get_contents($filename));
*
* @param string $filename
*
* @return $this The current instance
*/
public function textFromFile($filename)
{
$this->stack[] = array_merge([__FUNCTION__ . 'Collect'], func_get_args());
return $this;
}
/**
* substitute a placeholder with value, placeholder must be enclosed by `{}`.
*
* @param string $name
* @param string $val
*
* @return $this The current instance
*/
public function place($name, $val)
{
$this->replace('{'.$name.'}', $val);
return $this;
}
/**
* replace any string with value.
*
* @param string $string
* @param string $replacement
*
* @return $this The current instance
*/
public function replace($string, $replacement)
{
$this->stack[] = array_merge([__FUNCTION__ . 'Collect'], func_get_args());
return $this;
}
/**
* replace any string with value using regular expression.
*
* @param string $pattern
* @param string $replacement
*
* @return $this The current instance
*/
public function regexReplace($pattern, $replacement)
{
$this->stack[] = array_merge([__FUNCTION__ . 'Collect'], func_get_args());
return $this;
}
/**
* Append the provided text to the end of the buffer if the provided
* regex pattern matches any text already in the buffer.
*
* @param string $pattern
* @param string $text
*
* @return $this
*/
public function appendIfMatches($pattern, $text)
{
$this->stack[] = array_merge(['appendIfMatchesCollect'], [$pattern, $text, true]);
return $this;
}
/**
* Append the provided text to the end of the buffer unless the provided
* regex pattern matches any text already in the buffer.
*
* @param string $pattern
* @param string $text
*
* @return $this
*/
public function appendUnlessMatches($pattern, $text)
{
$this->stack[] = array_merge(['appendIfMatchesCollect'], [$pattern, $text, false]);
return $this;
}
/**
* @param $contents string
* @param $filename string
*
* @return string
*/
protected function textFromFileCollect($contents, $filename)
{
if (file_exists($filename)) {
$contents .= file_get_contents($filename);
}
return $contents;
}
/**
* @param string|string[] $contents
* @param string|string[] $string
* @param string|string[] $replacement
*
* @return string|string[]
*/
protected function replaceCollect($contents, $string, $replacement)
{
return str_replace($string, $replacement, $contents);
}
/**
* @param string|string[] $contents
* @param string|string[] $pattern
* @param string|string[] $replacement
*
* @return string|string[]
*/
protected function regexReplaceCollect($contents, $pattern, $replacement)
{
return preg_replace($pattern, $replacement, $contents);
}
/**
* @param string $contents
* @param string $text
*
* @return string
*/
protected function textCollect($contents, $text)
{
return $contents . $text;
}
/**
* @param string $contents
* @param string $pattern
* @param string $text
* @param bool $shouldMatch
*
* @return string
*/
protected function appendIfMatchesCollect($contents, $pattern, $text, $shouldMatch)
{
if (preg_match($pattern, $contents) == $shouldMatch) {
$contents .= $text;
}
return $contents;
}
/**
* @return string
*/
public function originalContents()
{
if (!isset($this->originalContents)) {
$this->originalContents = '';
if (file_exists($this->filename)) {
$this->originalContents = file_get_contents($this->filename);
}
}
return $this->originalContents;
}
/**
* @return bool
*/
public function wouldChange()
{
return $this->originalContents() != $this->getContentsToWrite();
}
/**
* @return string
*/
protected function getContentsToWrite()
{
$contents = "";
if ($this->append) {
$contents = $this->originalContents();
}
foreach ($this->stack as $action) {
$command = array_shift($action);
if (method_exists($this, $command)) {
array_unshift($action, $contents);
$contents = call_user_func_array([$this, $command], $action);
}
}
return $contents;
}
/**
* {@inheritdoc}
*/
public function run()
{
$this->printTaskInfo("Writing to {filename}.", ['filename' => $this->filename]);
$contents = $this->getContentsToWrite();
if (!file_exists(dirname($this->filename))) {
mkdir(dirname($this->filename), 0777, true);
}
$res = file_put_contents($this->filename, $contents);
if ($res === false) {
return Result::error($this, "File {$this->filename} couldn't be created");
}
return Result::success($this);
}
/**
* @return string
*/
public function getPath()
{
return $this->filename;
}
}
<?php
namespace Robo\Task\Filesystem;
use Robo\Task\BaseTask;
use Symfony\Component\Filesystem\Filesystem as sfFilesystem;
abstract class BaseDir extends BaseTask
{
/**
* @var string[]
*/
protected $dirs = [];
/**
* @var \Symfony\Component\Filesystem\Filesystem
*/
protected $fs;
/**
* @param string|string[] $dirs
*/
public function __construct($dirs)
{
is_array($dirs)
? $this->dirs = $dirs
: $this->dirs[] = $dirs;
$this->fs = new sfFilesystem();
}
}
<?php
namespace Robo\Task\Filesystem;
use Robo\Common\ResourceExistenceChecker;
use Robo\Result;
/**
* Deletes all files from specified dir, ignoring git files.
*
* ``` php
* <?php
* $this->taskCleanDir(['tmp','logs'])->run();
* // as shortcut
* $this->_cleanDir('app/cache');
* ?>
* ```
*/
class CleanDir extends BaseDir
{
use ResourceExistenceChecker;
/**
* {@inheritdoc}
*/
public function run()
{
if (!$this->checkResources($this->dirs, 'dir')) {
return Result::error($this, 'Source directories are missing!');
}
foreach ($this->dirs as $dir) {
$this->emptyDir($dir);
$this->printTaskInfo("Cleaned {dir}", ['dir' => $dir]);
}
return Result::success($this);
}
/**
* @param string $path
*/
protected function emptyDir($path)
{
$iterator = new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator($path),
\RecursiveIteratorIterator::CHILD_FIRST
);
foreach ($iterator as $path) {
if ($path->isDir()) {
$dir = (string)$path;
if (basename($dir) === '.' || basename($dir) === '..') {
continue;
}
$this->fs->remove($dir);
} else {
$file = (string)$path;
if (basename($file) === '.gitignore' || basename($file) === '.gitkeep') {
continue;
}
$this->fs->remove($file);
}
}
}
}
<?php
namespace Robo\Task\Filesystem;
use Robo\Common\ResourceExistenceChecker;
use Robo\Result;
use Robo\Exception\TaskException;
/**
* Copies one dir into another
*
* ``` php
* <?php
* $this->taskCopyDir(['dist/config' => 'config'])->run();
* // as shortcut
* $this->_copyDir('dist/config', 'config');
* ?>
* ```
*/
class CopyDir extends BaseDir
{
use ResourceExistenceChecker;
/**
* @var int
*/
protected $chmod = 0755;
/**
* Files to exclude on copying.
*
* @var string[]
*/
protected $exclude = [];
/**
* {@inheritdoc}
*/
public function run()
{
if (!$this->checkResources($this->dirs, 'dir')) {
return Result::error($this, 'Source directories are missing!');
}
foreach ($this->dirs as $src => $dst) {
$this->copyDir($src, $dst);
$this->printTaskInfo('Copied from {source} to {destination}', ['source' => $src, 'destination' => $dst]);
}
return Result::success($this);
}
/**
* Sets the default folder permissions for the destination if it doesn't exist
*
* @link http://en.wikipedia.org/wiki/Chmod
* @link http://php.net/manual/en/function.mkdir.php
* @link http://php.net/manual/en/function.chmod.php
*
* @param int $value
*
* @return $this
*/
public function dirPermissions($value)
{
$this->chmod = (int)$value;
return $this;
}
/**
* List files to exclude.
*
* @param string[] $exclude
*
* @return $this
*/
public function exclude($exclude = [])
{
$this->exclude = $exclude;
return $this;
}
/**
* Copies a directory to another location.
*
* @param string $src Source directory
* @param string $dst Destination directory
*
* @throws \Robo\Exception\TaskException
*/
protected function copyDir($src, $dst)
{
$dir = @opendir($src);
if (false === $dir) {
throw new TaskException($this, "Cannot open source directory '" . $src . "'");
}
if (!is_dir($dst)) {
mkdir($dst, $this->chmod, true);
}
while (false !== ($file = readdir($dir))) {
if (in_array($file, $this->exclude)) {
continue;
}
if (($file !== '.') && ($file !== '..')) {
$srcFile = $src . '/' . $file;
$destFile = $dst . '/' . $file;
if (is_dir($srcFile)) {
$this->copyDir($srcFile, $destFile);
} else {
copy($srcFile, $destFile);
}
}
}
closedir($dir);
}
}
<?php
namespace Robo\Task\Filesystem;
use Robo\Common\ResourceExistenceChecker;
use Robo\Result;
/**
* Deletes dir
*
* ``` php
* <?php
* $this->taskDeleteDir('tmp')->run();
* // as shortcut
* $this->_deleteDir(['tmp', 'log']);
* ?>
* ```
*/
class DeleteDir extends BaseDir
{
use ResourceExistenceChecker;
/**
* {@inheritdoc}
*/
public function run()
{
if (!$this->checkResources($this->dirs, 'dir')) {
return Result::error($this, 'Source directories are missing!');
}
foreach ($this->dirs as $dir) {
$this->fs->remove($dir);
$this->printTaskInfo("Deleted {dir}...", ['dir' => $dir]);
}
return Result::success($this);
}
}
<?php
namespace Robo\Task\Filesystem;
use Robo\Result;
use Robo\Task\StackBasedTask;
use Symfony\Component\Filesystem\Filesystem as sfFilesystem;
use Symfony\Component\Filesystem\Exception\IOExceptionInterface;
use Symfony\Component\Filesystem\Exception\IOException;
use Robo\Contract\BuilderAwareInterface;
use Robo\Common\BuilderAwareTrait;
/**
* Wrapper for [Symfony Filesystem](http://symfony.com/doc/current/components/filesystem.html) Component.
* Comands are executed in stack and can be stopped on first fail with `stopOnFail` option.
*
* ``` php
* <?php
* $this->taskFilesystemStack()
* ->mkdir('logs')
* ->touch('logs/.gitignore')
* ->chgrp('www', 'www-data')
* ->symlink('/var/log/nginx/error.log', 'logs/error.log')
* ->run();
*
* // one line
* $this->_touch('.gitignore');
* $this->_mkdir('logs');
*
* ?>
* ```
*
* @method $this mkdir($dir)
* @method $this touch($file)
* @method $this copy($from, $to, $force = null)
* @method $this chmod($file, $permissions, $umask = null, $recursive = null)
* @method $this chgrp($file, $group, $recursive = null)
* @method $this chown($file, $user, $recursive = null)
* @method $this remove($file)
* @method $this rename($from, $to)
* @method $this symlink($from, $to)
* @method $this mirror($from, $to)
*/
class FilesystemStack extends StackBasedTask implements BuilderAwareInterface
{
use BuilderAwareTrait;
/**
* @var \Symfony\Component\Filesystem\Filesystem
*/
protected $fs;
public function __construct()
{
$this->fs = new sfFilesystem();
}
/**
* @return \Symfony\Component\Filesystem\Filesystem
*/
protected function getDelegate()
{
return $this->fs;
}
/**
* @param string $from
* @param string $to
* @param bool $force
*/
protected function _copy($from, $to, $force = false)
{
$this->fs->copy($from, $to, $force);
}
/**
* @param string|string[]|\Traversable $file
* @param int $permissions
* @param int $umask
* @param bool $recursive
*/
protected function _chmod($file, $permissions, $umask = 0000, $recursive = false)
{
$this->fs->chmod($file, $permissions, $umask, $recursive);
}
/**
* @param string|string[]|\Traversable $file
* @param string $group
* @param bool $recursive
*/
protected function _chgrp($file, $group, $recursive = null)
{
$this->fs->chgrp($file, $group, $recursive);
}
/**
* @param string|string[]|\Traversable $file
* @param string $user
* @param bool $recursive
*/
protected function _chown($file, $user, $recursive = null)
{
$this->fs->chown($file, $user, $recursive);
}
/**
* @param string $origin
* @param string $target
* @param bool $overwrite
*
* @return null|true|\Robo\Result
*/
protected function _rename($origin, $target, $overwrite = false)
{
// we check that target does not exist
if ((!$overwrite && is_readable($target)) || (file_exists($target) && !is_writable($target))) {
throw new IOException(sprintf('Cannot rename because the target "%s" already exists.', $target), 0, null, $target);
}
// Due to a bug (limitation) in PHP, cross-volume renames do not work.
// See: https://bugs.php.net/bug.php?id=54097
if (true !== @rename($origin, $target)) {
return $this->crossVolumeRename($origin, $target);
}
return true;
}
/**
* @param string $origin
* @param string $target
*
* @return null|\Robo\Result
*/
protected function crossVolumeRename($origin, $target)
{
// First step is to try to get rid of the target. If there
// is a single, deletable file, then we will just unlink it.
if (is_file($target)) {
unlink($target);
}
// If the target still exists, we will try to delete it.
// TODO: Note that if this fails partway through, then we cannot
// adequately rollback. Perhaps we need to preflight the operation
// and determine if everything inside of $target is writable.
if (file_exists($target)) {
$this->fs->remove($target);
}
/** @var \Robo\Result $result */
$result = $this->collectionBuilder()->taskCopyDir([$origin => $target])->run();
if (!$result->wasSuccessful()) {
return $result;
}
$this->fs->remove($origin);
}
}
<?php
namespace Robo\Task\Filesystem;
use Robo\Result;
use Robo\Exception\TaskException;
use Symfony\Component\Finder\Finder;
/**
* Searches for files in a nested directory structure and copies them to
* a target directory with or without the parent directories. The task was
* inspired by [gulp-flatten](https://www.npmjs.com/package/gulp-flatten).
*
* Example directory structure:
*
* ```
* └── assets
* ├── asset-library1
* │ ├── README.md
* │ └── asset-library1.min.js
* └── asset-library2
* ├── README.md
* └── asset-library2.min.js
* ```
*
* The following code will search the `*.min.js` files and copy them
* inside a new `dist` folder:
*
* ``` php
* <?php
* $this->taskFlattenDir(['assets/*.min.js' => 'dist'])->run();
* // or use shortcut
* $this->_flattenDir('assets/*.min.js', 'dist');
* ?>
* ```
*
* You can also define the target directory with an additional method, instead of
* key/value pairs. More similar to the gulp-flatten syntax:
*
* ``` php
* <?php
* $this->taskFlattenDir(['assets/*.min.js'])
* ->to('dist')
* ->run();
* ?>
* ```
*
* You can also append parts of the parent directories to the target path. If you give
* the value `1` to the `includeParents()` method, then the top parent will be appended
* to the target directory resulting in a path such as `dist/assets/asset-library1.min.js`.
*
* If you give a negative number, such as `-1` (the same as specifying `array(0, 1)` then
* the bottom parent will be appended, resulting in a path such as
* `dist/asset-library1/asset-library1.min.js`.
*
* The top parent directory will always be starting from the relative path to the current
* directory. You can override that with the `parentDir()` method. If in the above example
* you would specify `assets`, then the top parent directory would be `asset-library1`.
*
* ``` php
* <?php
* $this->taskFlattenDir(['assets/*.min.js' => 'dist'])
* ->parentDir('assets')
* ->includeParents(1)
* ->run();
* ?>
* ```
*/
class FlattenDir extends BaseDir
{
/**
* @var int
*/
protected $chmod = 0755;
/**
* @var int[]
*/
protected $parents = array(0, 0);
/**
* @var string
*/
protected $parentDir = '';
/**
* @var string
*/
protected $to;
/**
* {@inheritdoc}
*/
public function __construct($dirs)
{
parent::__construct($dirs);
$this->parentDir = getcwd();
}
/**
* {@inheritdoc}
*/
public function run()
{
// find the files
$files = $this->findFiles($this->dirs);
// copy the files
$this->copyFiles($files);
$fileNoun = count($files) == 1 ? ' file' : ' files';
$this->printTaskSuccess("Copied {count} $fileNoun to {destination}", ['count' => count($files), 'destination' => $this->to]);
return Result::success($this);
}
/**
* Sets the default folder permissions for the destination if it does not exist.
*
* @link http://en.wikipedia.org/wiki/Chmod
* @link http://php.net/manual/en/function.mkdir.php
* @link http://php.net/manual/en/function.chmod.php
*
* @param int $permission
*
* @return $this
*/
public function dirPermissions($permission)
{
$this->chmod = (int) $permission;
return $this;
}
/**
* Sets the value from which direction and how much parent dirs should be included.
* Accepts a positive or negative integer or an array with two integer values.
*
* @param int|int[] $parents
*
* @return $this
*
* @throws TaskException
*/
public function includeParents($parents)
{
if (is_int($parents)) {
// if an integer is given check whether it is for top or bottom parent
if ($parents >= 0) {
$this->parents[0] = $parents;
return $this;
}
$this->parents[1] = 0 - $parents;
return $this;
}
if (is_array($parents)) {
// check if the array has two values no more, no less
if (count($parents) == 2) {
$this->parents = $parents;
return $this;
}
}
throw new TaskException($this, 'includeParents expects an integer or an array with two values');
}
/**
* Sets the parent directory from which the relative parent directories will be calculated.
*
* @param string $dir
*
* @return $this
*/
public function parentDir($dir)
{
if (!$this->fs->isAbsolutePath($dir)) {
// attach the relative path to current working directory
$dir = getcwd().'/'.$dir;
}
$this->parentDir = $dir;
return $this;
}
/**
* Sets the target directory where the files will be copied to.
*
* @param string $target
*
* @return $this
*/
public function to($target)
{
$this->to = rtrim($target, '/');
return $this;
}
/**
* @param array $dirs
*
* @return array|\Robo\Result
*
* @throws \Robo\Exception\TaskException
*/
protected function findFiles($dirs)
{
$files = array();
// find the files
foreach ($dirs as $k => $v) {
// reset finder
$finder = new Finder();
$dir = $k;
$to = $v;
// check if target was given with the to() method instead of key/value pairs
if (is_int($k)) {
$dir = $v;
if (isset($this->to)) {
$to = $this->to;
} else {
throw new TaskException($this, 'target directory is not defined');
}
}
try {
$finder->files()->in($dir);
} catch (\InvalidArgumentException $e) {
// if finder cannot handle it, try with in()->name()
if (strpos($dir, '/') === false) {
$dir = './'.$dir;
}
$parts = explode('/', $dir);
$new_dir = implode('/', array_slice($parts, 0, -1));
try {
$finder->files()->in($new_dir)->name(array_pop($parts));
} catch (\InvalidArgumentException $e) {
return Result::fromException($this, $e);
}
}
foreach ($finder as $file) {
// store the absolute path as key and target as value in the files array
$files[$file->getRealpath()] = $this->getTarget($file->getRealPath(), $to);
}
$fileNoun = count($files) == 1 ? ' file' : ' files';
$this->printTaskInfo("Found {count} $fileNoun in {dir}", ['count' => count($files), 'dir' => $dir]);
}
return $files;
}
/**
* @param string $file
* @param string $to
*
* @return string
*/
protected function getTarget($file, $to)
{
$target = $to.'/'.basename($file);
if ($this->parents !== array(0, 0)) {
// if the parent is set, create additional directories inside target
// get relative path to parentDir
$rel_path = $this->fs->makePathRelative(dirname($file), $this->parentDir);
// get top parents and bottom parents
$parts = explode('/', rtrim($rel_path, '/'));
$prefix_dir = '';
$prefix_dir .= ($this->parents[0] > 0 ? implode('/', array_slice($parts, 0, $this->parents[0])).'/' : '');
$prefix_dir .= ($this->parents[1] > 0 ? implode('/', array_slice($parts, (0 - $this->parents[1]), $this->parents[1])) : '');
$prefix_dir = rtrim($prefix_dir, '/');
$target = $to.'/'.$prefix_dir.'/'.basename($file);
}
return $target;
}
/**
* @param array $files
*/
protected function copyFiles($files)
{
// copy the files
foreach ($files as $from => $to) {
// check if target dir exists
if (!is_dir(dirname($to))) {
$this->fs->mkdir(dirname($to), $this->chmod);
}
$this->fs->copy($from, $to);
}
}
}
<?php
namespace Robo\Task\Filesystem;
use Robo\Collection\Temporary;
trait loadShortcuts
{
/**
* @param string $src
* @param string $dst
*
* @return \Robo\Result
*/
protected function _copyDir($src, $dst)
{
return $this->taskCopyDir([$src => $dst])->run();
}
/**
* @param string $src
* @param string $dst
*
* @return \Robo\Result
*/
protected function _mirrorDir($src, $dst)
{
return $this->taskMirrorDir([$src => $dst])->run();
}
/**
* @param string|string[] $dir
*
* @return \Robo\Result
*/
protected function _deleteDir($dir)
{
return $this->taskDeleteDir($dir)->run();
}
/**
* @param string|string[] $dir
*
* @return \Robo\Result
*/
protected function _cleanDir($dir)
{
return $this->taskCleanDir($dir)->run();
}
/**
* @param string $from
* @param string $to
*
* @return \Robo\Result
*/
protected function _rename($from, $to)
{
return $this->taskFilesystemStack()->rename($from, $to)->run();
}
/**
* @param string|string[] $dir
*
* @return \Robo\Result
*/
protected function _mkdir($dir)
{
return $this->taskFilesystemStack()->mkdir($dir)->run();
}
/**
* @param string $prefix
* @param string $base
* @param bool $includeRandomPart
*
* @return string
*/
protected function _tmpDir($prefix = 'tmp', $base = '', $includeRandomPart = true)
{
$result = $this->taskTmpDir($prefix, $base, $includeRandomPart)->run();
return isset($result['path']) ? $result['path'] : '';
}
/**
* @param string $file
*
* @return \Robo\Result
*/
protected function _touch($file)
{
return $this->taskFilesystemStack()->touch($file)->run();
}
/**
* @param string|string[] $file
*
* @return \Robo\Result
*/
protected function _remove($file)
{
return $this->taskFilesystemStack()->remove($file)->run();
}
/**
* @param string|string[] $file
* @param string $group
*
* @return \Robo\Result
*/
protected function _chgrp($file, $group)
{
return $this->taskFilesystemStack()->chgrp($file, $group)->run();
}
/**
* @param string|string[] $file
* @param int $permissions
* @param int $umask
* @param bool $recursive
*
* @return \Robo\Result
*/
protected function _chmod($file, $permissions, $umask = 0000, $recursive = false)
{
return $this->taskFilesystemStack()->chmod($file, $permissions, $umask, $recursive)->run();
}
/**
* @param string $from
* @param string $to
*
* @return \Robo\Result
*/
protected function _symlink($from, $to)
{
return $this->taskFilesystemStack()->symlink($from, $to)->run();
}
/**
* @param string $from
* @param string $to
*
* @return \Robo\Result
*/
protected function _copy($from, $to)
{
return $this->taskFilesystemStack()->copy($from, $to)->run();
}
/**
* @param string $from
* @param string $to
*
* @return \Robo\Result
*/
protected function _flattenDir($from, $to)
{
return $this->taskFlattenDir([$from => $to])->run();
}
}
<?php
namespace Robo\Task\Filesystem;
use Robo\Collection\Temporary;
trait loadTasks
{
/**
* @param string|string[] $dirs
*
* @return \Robo\Task\Filesystem\CleanDir
*/
protected function taskCleanDir($dirs)
{
return $this->task(CleanDir::class, $dirs);
}
/**
* @param string|string[] $dirs
*
* @return \Robo\Task\Filesystem\DeleteDir
*/
protected function taskDeleteDir($dirs)
{
return $this->task(DeleteDir::class, $dirs);
}
/**
* @param string $prefix
* @param string $base
* @param bool $includeRandomPart
*
* @return \Robo\Task\Filesystem\WorkDir
*/
protected function taskTmpDir($prefix = 'tmp', $base = '', $includeRandomPart = true)
{
return $this->task(TmpDir::class, $prefix, $base, $includeRandomPart);
}
/**
* @param string $finalDestination
*
* @return \Robo\Task\Filesystem\TmpDir
*/
protected function taskWorkDir($finalDestination)
{
return $this->task(WorkDir::class, $finalDestination);
}
/**
* @param string|string[] $dirs
*
* @return \Robo\Task\Filesystem\CopyDir
*/
protected function taskCopyDir($dirs)
{
return $this->task(CopyDir::class, $dirs);
}
/**
* @param string|string[] $dirs
*
* @return \Robo\Task\Filesystem\MirrorDir
*/
protected function taskMirrorDir($dirs)
{
return $this->task(MirrorDir::class, $dirs);
}
/**
* @param string|string[] $dirs
*
* @return \Robo\Task\Filesystem\FlattenDir
*/
protected function taskFlattenDir($dirs)
{
return $this->task(FlattenDir::class, $dirs);
}
/**
* @return \Robo\Task\Filesystem\FilesystemStack
*/
protected function taskFilesystemStack()
{
return $this->task(FilesystemStack::class);
}
}
<?php
namespace Robo\Task\Filesystem;
use Robo\Result;
/**
* Mirrors a directory to another
*
* ``` php
* <?php
* $this->taskMirrorDir(['dist/config/' => 'config/'])->run();
* // or use shortcut
* $this->_mirrorDir('dist/config/', 'config/');
*
* ?>
* ```
*/
class MirrorDir extends BaseDir
{
/**
* {@inheritdoc}
*/
public function run()
{
foreach ($this->dirs as $src => $dst) {
$this->fs->mirror(
$src,
$dst,
null,
[
'override' => true,
'copy_on_windows' => true,
'delete' => true
]
);
$this->printTaskInfo("Mirrored from {source} to {destination}", ['source' => $src, 'destination' => $dst]);
}
return Result::success($this);
}
}
<?php
namespace Robo\Task\Filesystem;
use Robo\Result;
use Robo\Collection\Collection;
use Robo\Contract\CompletionInterface;
/**
* Create a temporary directory that is automatically cleaned up
* once the task collection is is part of completes.
*
* Use WorkDir if you do not want the directory to be deleted.
*
* ``` php
* <?php
* // Delete on rollback or on successful completion.
* // Note that in this example, everything is deleted at
* // the end of $collection->run().
* $collection = $this->collectionBuilder();
* $tmpPath = $collection->tmpDir()->getPath();
* $collection->taskFilesystemStack()
* ->mkdir("$tmpPath/log")
* ->touch("$tmpPath/log/error.txt");
* $collection->run();
* // as shortcut (deleted when program exits)
* $tmpPath = $this->_tmpDir();
* ?>
* ```
*/
class TmpDir extends BaseDir implements CompletionInterface
{
/**
* @var string
*/
protected $base;
/**
* @var string
*/
protected $prefix;
/**
* @var bool
*/
protected $cwd;
/**
* @var string
*/
protected $savedWorkingDirectory;
/**
* @param string $prefix
* @param string $base
* @param bool $includeRandomPart
*/
public function __construct($prefix = 'tmp', $base = '', $includeRandomPart = true)
{
if (empty($base)) {
$base = sys_get_temp_dir();
}
$path = "{$base}/{$prefix}";
if ($includeRandomPart) {
$path = static::randomLocation($path);
}
parent::__construct(["$path"]);
}
/**
* Add a random part to a path, ensuring that the directory does
* not (currently) exist.
*
* @param string $path The base/prefix path to add a random component to
* @param int $length Number of digits in the random part
*
* @return string
*/
protected static function randomLocation($path, $length = 12)
{
$random = static::randomString($length);
while (is_dir("{$path}_{$random}")) {
$random = static::randomString($length);
}
return "{$path}_{$random}";
}
/**
* Generate a suitably random string to use as the suffix for our
* temporary directory.
*
* @param int $length
*
* @return string
*/
protected static function randomString($length = 12)
{
return substr(str_shuffle('23456789abcdefghjkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ'), 0, max($length, 3));
}
/**
* Flag that we should cwd to the temporary directory when it is
* created, and restore the old working directory when it is deleted.
*
* @param bool $shouldChangeWorkingDirectory
*
* @return $this
*/
public function cwd($shouldChangeWorkingDirectory = true)
{
$this->cwd = $shouldChangeWorkingDirectory;
return $this;
}
/**
* {@inheritdoc}
*/
public function run()
{
// Save the current working directory
$this->savedWorkingDirectory = getcwd();
foreach ($this->dirs as $dir) {
$this->fs->mkdir($dir);
$this->printTaskInfo("Created {dir}...", ['dir' => $dir]);
// Change the current working directory, if requested
if ($this->cwd) {
chdir($dir);
}
}
return Result::success($this, '', ['path' => $this->getPath()]);
}
protected function restoreWorkingDirectory()
{
// Restore the current working directory, if we redirected it.
if ($this->cwd) {
chdir($this->savedWorkingDirectory);
}
}
protected function deleteTmpDir()
{
foreach ($this->dirs as $dir) {
$this->fs->remove($dir);
}
}
/**
* Delete this directory when our collection completes.
* If this temporary directory is not part of a collection,
* then it will be deleted when the program terminates,
* presuming that it was created by taskTmpDir() or _tmpDir().
*/
public function complete()
{
$this->restoreWorkingDirectory();
$this->deleteTmpDir();
}
/**
* Get a reference to the path to the temporary directory, so that
* it may be used to create other tasks. Note that the directory
* is not actually created until the task runs.
*
* @return string
*/
public function getPath()
{
return $this->dirs[0];
}
}
<?php
namespace Robo\Task\Filesystem;
use Robo\Result;
use Robo\Collection\Collection;
use Robo\Contract\CompletionInterface;
use Robo\Contract\RollbackInterface;
use Robo\Contract\BuilderAwareInterface;
use Robo\Common\BuilderAwareTrait;
/**
* Create a temporary working directory that is automatically renamed to its
* final desired location if all of the tasks in the collection succeed. If
* there is a rollback, then the working directory is deleted.
*
* ``` php
* <?php
* $collection = $this->collectionBuilder();
* $workingPath = $collection->workDir("build")->getPath();
* $collection->taskFilesystemStack()
* ->mkdir("$workingPath/log")
* ->touch("$workingPath/log/error.txt");
* $collection->run();
* ?>
* ```
*/
class WorkDir extends TmpDir implements RollbackInterface, BuilderAwareInterface
{
use BuilderAwareTrait;
/**
* @var string
*/
protected $finalDestination;
/**
* @param string $finalDestination
*/
public function __construct($finalDestination)
{
$this->finalDestination = $finalDestination;
// Create a temporary directory to work in. We will place our
// temporary directory in the same location as the final destination
// directory, so that the work directory can be moved into place
// without having to be copied, e.g. in a cross-volume rename scenario.
parent::__construct(basename($finalDestination), dirname($finalDestination));
}
/**
* Create our working directory.
*
* @return \Robo\Result
*/
public function run()
{
// Destination cannot be empty
if (empty($this->finalDestination)) {
return Result::error($this, "Destination directory not specified.");
}
// Before we do anything else, ensure that any directory in the
// final destination is writable, so that we can at a minimum
// move it out of the way before placing our results there.
if (is_dir($this->finalDestination)) {
if (!is_writable($this->finalDestination)) {
return Result::error($this, "Destination directory {dir} exists and cannot be overwritten.", ['dir' => $this->finalDestination]);
}
}
return parent::run();
}
/**
* Move our working directory into its final destination once the
* collection it belongs to completes.
*/
public function complete()
{
$this->restoreWorkingDirectory();
// Delete the final destination, if it exists.
// Move it out of the way first, in case it cannot
// be completely deleted.
if (file_exists($this->finalDestination)) {
$temporaryLocation = static::randomLocation($this->finalDestination . '_TO_DELETE_');
// This should always work, because we already created a temporary
// folder in the parent directory of the final destination, and we
// have already checked to confirm that the final destination is
// writable.
rename($this->finalDestination, $temporaryLocation);
// This may silently fail, leaving artifacts behind, if there
// are permissions problems with some items somewhere inside
// the folder being deleted.
$this->fs->remove($temporaryLocation);
}
// Move our working directory over the final destination.
// This should never be a cross-volume rename, so this should
// always succeed.
$workDir = reset($this->dirs);
if (file_exists($workDir)) {
rename($workDir, $this->finalDestination);
}
}
/**
* Delete our working directory
*/
public function rollback()
{
$this->restoreWorkingDirectory();
$this->deleteTmpDir();
}
/**
* Get a reference to the path to the temporary directory, so that
* it may be used to create other tasks. Note that the directory
* is not actually created until the task runs.
*
* @return string
*/
public function getPath()
{
return $this->dirs[0];
}
}
<?php
namespace Robo\Task\Gulp;
use Robo\Task\BaseTask;
use Robo\Exception\TaskException;
use Symfony\Component\Process\ProcessUtils;
abstract class Base extends BaseTask
{
use \Robo\Common\ExecOneCommand;
/**
* @var string
*/
protected $command = '';
/**
* @var array
*/
protected $opts = [];
/**
* @var string
*/
protected $task = '';
/**
* adds `silent` option to gulp
*
* @return $this
*/
public function silent()
{
$this->option('silent');
return $this;
}
/**
* adds `--no-color` option to gulp
*
* @return $this
*/
public function noColor()
{
$this->option('no-color');
return $this;
}
/**
* adds `--color` option to gulp
*
* @return $this
*/
public function color()
{
$this->option('color');
return $this;
}
/**
* adds `--tasks-simple` option to gulp
*
* @return $this
*/
public function simple()
{
$this->option('tasks-simple');
return $this;
}
/**
* @param string $task
* @param null|string $pathToGulp
*
* @throws \Robo\Exception\TaskException
*/
public function __construct($task, $pathToGulp = null)
{
$this->task = $task;
$this->command = $pathToGulp;
if (!$this->command) {
$this->command = $this->findExecutable('gulp');
}
if (!$this->command) {
throw new TaskException(__CLASS__, "Gulp executable not found.");
}
}
/**
* @return string
*/
public function getCommand()
{
return "{$this->command} " . ProcessUtils::escapeArgument($this->task) . "{$this->arguments}";
}
}
<?php
namespace Robo\Task\Gulp;
trait loadTasks
{
/**
* @param string $task
* @param null|string $pathToGulp
*
* @return \Robo\Task\Gulp\Run
*/
protected function taskGulpRun($task = 'default', $pathToGulp = null)
{
return $this->task(Run::class, $task, $pathToGulp);
}
}
<?php
namespace Robo\Task\Gulp;
use Robo\Task\Gulp;
use Robo\Contract\CommandInterface;
/**
* Gulp Run
*
* ``` php
* <?php
* // simple execution
* $this->taskGulpRun()->run();
*
* // run task 'clean' with --silent option
* $this->taskGulpRun('clean')
* ->silent()
* ->run();
* ?>
* ```
*/
class Run extends Base implements CommandInterface
{
/**
* {@inheritdoc}
*/
public function run()
{
if (strlen($this->arguments)) {
$this->printTaskInfo('Running Gulp task: {gulp_task} with arguments: {arguments}', ['gulp_task' => $this->task, 'arguments' => $this->arguments]);
} else {
$this->printTaskInfo('Running Gulp task: {gulp_task} without arguments', ['gulp_task' => $this->task]);
}
return $this->executeCommand($this->getCommand());
}
}
<?php
namespace Robo\Task\Npm;
use Robo\Task\BaseTask;
use Robo\Exception\TaskException;
abstract class Base extends BaseTask
{
use \Robo\Common\ExecOneCommand;
/**
* @var string
*/
protected $command = '';
/**
* @var string[]
*/
protected $opts = [];
/**
* @var string
*/
protected $action = '';
/**
* adds `production` option to npm
*
* @return $this
*/
public function noDev()
{
$this->option('production');
return $this;
}
/**
* @param null|string $pathToNpm
*
* @throws \Robo\Exception\TaskException
*/
public function __construct($pathToNpm = null)
{
$this->command = $pathToNpm;
if (!$this->command) {
$this->command = $this->findExecutable('npm');
}
if (!$this->command) {
throw new TaskException(__CLASS__, "Npm executable not found.");
}
}
/**
* @return string
*/
public function getCommand()
{
return "{$this->command} {$this->action}{$this->arguments}";
}
}
<?php
namespace Robo\Task\Npm;
use Robo\Contract\CommandInterface;
/**
* Npm Install
*
* ``` php
* <?php
* // simple execution
* $this->taskNpmInstall()->run();
*
* // prefer dist with custom path
* $this->taskNpmInstall('path/to/my/npm')
* ->noDev()
* ->run();
* ?>
* ```
*/
class Install extends Base implements CommandInterface
{
/**
* @var string
*/
protected $action = 'install';
/**
* {@inheritdoc}
*/
public function run()
{
$this->printTaskInfo('Install Npm packages: {arguments}', ['arguments' => $this->arguments]);
return $this->executeCommand($this->getCommand());
}
}
<?php
namespace Robo\Task\Npm;
trait loadTasks
{
/**
* @param null|string $pathToNpm
*
* @return \Robo\Task\Npm\Install
*/
protected function taskNpmInstall($pathToNpm = null)
{
return $this->task(Install::class, $pathToNpm);
}
/**
* @param null|string $pathToNpm
*
* @return \Robo\Task\Npm\Update
*/
protected function taskNpmUpdate($pathToNpm = null)
{
return $this->task(Update::class, $pathToNpm);
}
}
<?php
namespace Robo\Task\Npm;
/**
* Npm Update
*
* ```php
* <?php
* // simple execution
* $this->taskNpmUpdate()->run();
*
* // prefer dist with custom path
* $this->taskNpmUpdate('path/to/my/npm')
* ->noDev()
* ->run();
* ?>
* ```
*/
class Update extends Base
{
/**
* @var string
*/
protected $action = 'update';
/**
* {@inheritdoc}
*/
public function run()
{
$this->printTaskInfo('Update Npm packages: {arguments}', ['arguments' => $this->arguments]);
return $this->executeCommand($this->getCommand());
}
}
<?php
namespace Robo\Task\Remote;
trait loadTasks
{
/**
* @return \Robo\Task\Remote\Rsync
*/
protected function taskRsync()
{
return $this->task(Rsync::class);
}
/**
* @param null|string $hostname
* @param null|string $user
*
* @return \Robo\Task\Remote\Ssh
*/
protected function taskSshExec($hostname = null, $user = null)
{
return $this->task(Ssh::class, $hostname, $user);
}
}
<?php
namespace Robo\Task\Remote;
use Robo\Contract\CommandInterface;
use Robo\Task\BaseTask;
use Robo\Task\Remote;
use Robo\Exception\TaskException;
/**
* Executes rsync in a flexible manner.
*
* ``` php
* $this->taskRsync()
* ->fromPath('src/')
* ->toHost('localhost')
* ->toUser('dev')
* ->toPath('/var/www/html/app/')
* ->remoteShell('ssh -i public_key')
* ->recursive()
* ->excludeVcs()
* ->checksum()
* ->wholeFile()
* ->verbose()
* ->progress()
* ->humanReadable()
* ->stats()
* ->run();
* ```
*
* You could also clone the task and do a dry-run first:
*
* ``` php
* $rsync = $this->taskRsync()
* ->fromPath('src/')
* ->toPath('example.com:/var/www/html/app/')
* ->archive()
* ->excludeVcs()
* ->progress()
* ->stats();
*
* $dryRun = clone $rsync;
* $dryRun->dryRun()->run();
* if ('y' === $this->ask('Do you want to run (y/n)')) {
* $rsync->run();
* }
* ```
*
* @method \Robo\Task\Remote\Rsync fromUser(string $user)
* @method \Robo\Task\Remote\Rsync fromHost(string $hostname)
* @method \Robo\Task\Remote\Rsync toUser(string $user)
* @method \Robo\Task\Remote\Rsync toHost(string $hostname)
*/
class Rsync extends BaseTask implements CommandInterface
{
use \Robo\Common\ExecOneCommand;
/**
* @var string
*/
protected $command;
/**
* @var string
*/
protected $fromUser;
/**
* @var string
*/
protected $fromHost;
/**
* @var string
*/
protected $fromPath;
/**
* @var string
*/
protected $toUser;
/**
* @var string
*/
protected $toHost;
/**
* @var string
*/
protected $toPath;
/**
* @return static
*/
public static function init()
{
return new static();
}
public function __construct()
{
$this->command = 'rsync';
}
/**
* This can either be a full rsync path spec (user@host:path) or just a path.
* In case of the former do not specify host and user.
*
* @param string|array $path
*
* @return $this
*/
public function fromPath($path)
{
$this->fromPath = $path;
return $this;
}
/**
* This can either be a full rsync path spec (user@host:path) or just a path.
* In case of the former do not specify host and user.
*
* @param string $path
*
* @return $this
*/
public function toPath($path)
{
$this->toPath = $path;
return $this;
}
/**
* @param string $fromUser
*
* @return $this
*/
public function fromUser($fromUser)
{
$this->fromUser = $fromUser;
return $this;
}
/**
* @param string $fromHost
*
* @return $this
*/
public function fromHost($fromHost)
{
$this->fromHost = $fromHost;
return $this;
}
/**
* @param string $toUser
*
* @return $this
*/
public function toUser($toUser)
{
$this->toUser = $toUser;
return $this;
}
/**
* @param string $toHost
*
* @return $this
*/
public function toHost($toHost)
{
$this->toHost = $toHost;
return $this;
}
/**
* @return $this
*/
public function progress()
{
$this->option(__FUNCTION__);
return $this;
}
/**
* @return $this
*/
public function stats()
{
$this->option(__FUNCTION__);
return $this;
}
/**
* @return $this
*/
public function recursive()
{
$this->option(__FUNCTION__);
return $this;
}
/**
* @return $this
*/
public function verbose()
{
$this->option(__FUNCTION__);
return $this;
}
/**
* @return $this
*/
public function checksum()
{
$this->option(__FUNCTION__);
return $this;
}
/**
* @return $this
*/
public function archive()
{
$this->option(__FUNCTION__);
return $this;
}
/**
* @return $this
*/
public function compress()
{
$this->option(__FUNCTION__);
return $this;
}
/**
* @return $this
*/
public function owner()
{
$this->option(__FUNCTION__);
return $this;
}
/**
* @return $this
*/
public function group()
{
$this->option(__FUNCTION__);
return $this;
}
/**
* @return $this
*/
public function times()
{
$this->option(__FUNCTION__);
return $this;
}
/**
* @return $this
*/
public function delete()
{
$this->option(__FUNCTION__);
return $this;
}
/**
* @param int $seconds
*
* @return $this
*/
public function timeout($seconds)
{
$this->option(__FUNCTION__, $seconds);
return $this;
}
/**
* @return $this
*/
public function humanReadable()
{
$this->option('human-readable');
return $this;
}
/**
* @return $this
*/
public function wholeFile()
{
$this->option('whole-file');
return $this;
}
/**
* @return $this
*/
public function dryRun()
{
$this->option('dry-run');
return $this;
}
/**
* @return $this
*/
public function itemizeChanges()
{
$this->option('itemize-changes');
return $this;
}
/**
* Excludes .git, .svn and .hg items at any depth.
*
* @return $this
*/
public function excludeVcs()
{
return $this->exclude([
'.git',
'.svn',
'.hg',
]);
}
/**
* @param array|string $pattern
*
* @return $this
*/
public function exclude($pattern)
{
return $this->optionList(__FUNCTION__, $pattern);
}
/**
* @param string $file
*
* @return $this
*
* @throws \Robo\Exception\TaskException
*/
public function excludeFrom($file)
{
if (!is_readable($file)) {
throw new TaskException($this, "Exclude file $file is not readable");
}
return $this->option('exclude-from', $file);
}
/**
* @param array|string $pattern
*
* @return $this
*/
public function includeFilter($pattern)
{
return $this->optionList('include', $pattern);
}
/**
* @param array|string $pattern
*
* @return $this
*/
public function filter($pattern)
{
return $this->optionList(__FUNCTION__, $pattern);
}
/**
* @param string $file
*
* @return $this
*
* @throws \Robo\Exception\TaskException
*/
public function filesFrom($file)
{
if (!is_readable($file)) {
throw new TaskException($this, "Files-from file $file is not readable");
}
return $this->option('files-from', $file);
}
/**
* @param string $command
*
* @return $this
*/
public function remoteShell($command)
{
$this->option('rsh', "'$command'");
return $this;
}
/**
* {@inheritdoc}
*/
public function run()
{
$command = $this->getCommand();
$this->printTaskInfo("Running {command}", ['command' => $command]);
return $this->executeCommand($command);
}
/**
* Returns command that can be executed.
* This method is used to pass generated command from one task to another.
*
* @return string
*/
public function getCommand()
{
foreach ((array)$this->fromPath as $from) {
$this->option(null, $this->getFromPathSpec($from));
}
$this->option(null, $this->getToPathSpec());
return $this->command . $this->arguments;
}
/**
* @return string
*/
protected function getFromPathSpec($from)
{
return $this->getPathSpec($this->fromHost, $this->fromUser, $from);
}
/**
* @return string
*/
protected function getToPathSpec()
{
return $this->getPathSpec($this->toHost, $this->toUser, $this->toPath);
}
/**
* @param string $host
* @param string $user
* @param string $path
*
* @return string
*/
protected function getPathSpec($host, $user, $path)
{
$spec = isset($path) ? $path : '';
if (!empty($host)) {
$spec = "{$host}:{$spec}";
}
if (!empty($user)) {
$spec = "{$user}@{$spec}";
}
return $spec;
}
}
<?php
namespace Robo\Task\Remote;
use Robo\Result;
use Robo\Contract\CommandInterface;
use Robo\Exception\TaskException;
use Robo\Task\BaseTask;
use Robo\Contract\SimulatedInterface;
/**
* Runs multiple commands on a remote server.
* Per default, commands are combined with &&, unless stopOnFail is false.
*
* ```php
* <?php
*
* $this->taskSshExec('remote.example.com', 'user')
* ->remoteDir('/var/www/html')
* ->exec('ls -la')
* ->exec('chmod g+x logs')
* ->run();
*
* ```
*
* You can even exec other tasks (which implement CommandInterface):
*
* ```php
* $gitTask = $this->taskGitStack()
* ->checkout('master')
* ->pull();
*
* $this->taskSshExec('remote.example.com')
* ->remoteDir('/var/www/html/site')
* ->exec($gitTask)
* ->run();
* ```
*
* You can configure the remote directory for all future calls:
*
* ```php
* \Robo\Task\Remote\Ssh::configure('remoteDir', '/some-dir');
* ```
*
* @method $this stopOnFail(bool $stopOnFail) Whether or not to chain commands together with &&
* and stop the chain if one command fails
* @method $this remoteDir(string $remoteWorkingDirectory) Changes to the given directory before running commands
*/
class Ssh extends BaseTask implements CommandInterface, SimulatedInterface
{
use \Robo\Common\CommandReceiver;
use \Robo\Common\ExecOneCommand;
/**
* @var null|string
*/
protected $hostname;
/**
* @var null|string
*/
protected $user;
/**
* @var bool
*/
protected $stopOnFail = true;
/**
* @var array
*/
protected $exec = [];
/**
* Changes to the given directory before running commands.
*
* @var string
*/
protected $remoteDir;
/**
* @param null|string $hostname
* @param null|string $user
*/
public function __construct($hostname = null, $user = null)
{
$this->hostname = $hostname;
$this->user = $user;
}
/**
* @param string $hostname
*
* @return $this
*/
public function hostname($hostname)
{
$this->hostname = $hostname;
return $this;
}
/**
* @param string $user
*
* @return $this
*/
public function user($user)
{
$this->user = $user;
return $this;
}
/**
* @param bool $stopOnFail
*
* @return $this
*/
public function stopOnFail($stopOnFail = true)
{
$this->stopOnFail = $stopOnFail;
return $this;
}
/**
* @param string $remoteDir
*
* @return $this
*/
public function remoteDir($remoteDir)
{
$this->remoteDir = $remoteDir;
return $this;
}
/**
* @param string $filename
*
* @return $this
*/
public function identityFile($filename)
{
$this->option('-i', $filename);
return $this;
}
/**
* @param int $port
*
* @return $this
*/
public function port($port)
{
$this->option('-p', $port);
return $this;
}
/**
* @return $this
*/
public function forcePseudoTty()
{
$this->option('-t');
return $this;
}
/**
* @return $this
*/
public function quiet()
{
$this->option('-q');
return $this;
}
/**
* @return $this
*/
public function verbose()
{
$this->option('-v');
return $this;
}
/**
* @param string|string[]|CommandInterface $command
*
* @return $this
*/
public function exec($command)
{
if (is_array($command)) {
$command = implode(' ', array_filter($command));
}
$this->exec[] = $command;
return $this;
}
/**
* Returns command that can be executed.
* This method is used to pass generated command from one task to another.
*
* @return string
*/
public function getCommand()
{
$commands = [];
foreach ($this->exec as $command) {
$commands[] = $this->receiveCommand($command);
}
$remoteDir = $this->remoteDir ? $this->remoteDir : $this->getConfigValue('remoteDir');
if (!empty($remoteDir)) {
array_unshift($commands, sprintf('cd "%s"', $remoteDir));
}
$command = implode($this->stopOnFail ? ' && ' : ' ; ', $commands);
return $this->sshCommand($command);
}
/**
* {@inheritdoc}
*/
public function run()
{
$this->validateParameters();
$command = $this->getCommand();
return $this->executeCommand($command);
}
/**
* {@inheritdoc}
*/
public function simulate($context)
{
$command = $this->getCommand();
$this->printTaskInfo("Running {command}", ['command' => $command] + $context);
}
protected function validateParameters()
{
if (empty($this->hostname)) {
throw new TaskException($this, 'Please set a hostname');
}
if (empty($this->exec)) {
throw new TaskException($this, 'Please add at least one command');
}
}
/**
* Returns an ssh command string running $command on the remote.
*
* @param string|CommandInterface $command
*
* @return string
*/
protected function sshCommand($command)
{
$command = $this->receiveCommand($command);
$sshOptions = $this->arguments;
$hostSpec = $this->hostname;
if ($this->user) {
$hostSpec = $this->user . '@' . $hostSpec;
}
return sprintf("ssh{$sshOptions} {$hostSpec} '{$command}'");
}
}
<?php
namespace Robo\Task;
use Robo\Contract\WrappedTaskInterface;
use Robo\Exception\TaskException;
use Robo\TaskInfo;
use Robo\Result;
use Robo\Contract\TaskInterface;
use Robo\Contract\SimulatedInterface;
use Robo\Log\RoboLogLevel;
use Psr\Log\LogLevel;
use Robo\Contract\CommandInterface;
class Simulator extends BaseTask implements CommandInterface
{
/**
* @var \Robo\Contract\TaskInterface
*/
protected $task;
/**
* @var array
*/
protected $constructorParameters;
/**
* @var array
*/
protected $stack = [];
/**
* @param \Robo\Contract\TaskInterface $task
* @param array $constructorParameters
*/
public function __construct(TaskInterface $task, $constructorParameters)
{
// TODO: If we ever want to convert the simulated task back into
// an executable task, then we should save the wrapped task.
$this->task = ($task instanceof WrappedTaskInterface) ? $task->original() : $task;
$this->constructorParameters = $constructorParameters;
}
/**
* @param string $function
* @param array $args
*
* @return \Robo\Result|\Robo\Task\Simulator
*/
public function __call($function, $args)
{
$this->stack[] = array_merge([$function], $args);
$result = call_user_func_array([$this->task, $function], $args);
return $result == $this->task ? $this : $result;
}
/**
* {@inheritdoc}
*/
public function run()
{
$callchain = '';
foreach ($this->stack as $action) {
$command = array_shift($action);
$parameters = $this->formatParameters($action);
$callchain .= "\n ->$command(<fg=green>$parameters</>)";
}
$context = $this->getTaskContext(
[
'_level' => RoboLogLevel::SIMULATED_ACTION,
'simulated' => TaskInfo::formatTaskName($this->task),
'parameters' => $this->formatParameters($this->constructorParameters),
'_style' => ['simulated' => 'fg=blue;options=bold'],
]
);
// RoboLogLevel::SIMULATED_ACTION
$this->printTaskInfo(
"Simulating {simulated}({parameters})$callchain",
$context
);
$result = null;
if ($this->task instanceof SimulatedInterface) {
$result = $this->task->simulate($context);
}
if (!isset($result)) {
$result = Result::success($this);
}
return $result;
}
/**
* Danger: reach through the simulated wrapper and pull out the command
* to be executed. This is used when using a simulated task with another
* simulated task that runs commands, e.g. the Remote\Ssh task. Using
* a simulated CommandInterface task with a non-simulated task may produce
* unexpected results (e.g. execution!).
*
* @return string
*
* @throws \Robo\Exception\TaskException
*/
public function getCommand()
{
if (!$this->task instanceof CommandInterface) {
throw new TaskException($this->task, 'Simulated task that is not a CommandInterface used as a CommandInterface.');
}
return $this->task->getCommand();
}
/**
* @param string $action
*
* @return string
*/
protected function formatParameters($action)
{
$parameterList = array_map([$this, 'convertParameter'], $action);
return implode(', ', $parameterList);
}
/**
* @param mixed $item
*
* @return string
*/
protected function convertParameter($item)
{
if (is_callable($item)) {
return 'inline_function(...)';
}
if (is_array($item)) {
return $this->shortenParameter(var_export($item, true));
}
if (is_object($item)) {
return '[' . get_class($item). ' object]';
}
if (is_string($item)) {
return $this->shortenParameter("'$item'");
}
if (is_null($item)) {
return 'null';
}
return $item;
}
/**
* @param string $item
* @param string $shortForm
*
* @return string
*/
protected function shortenParameter($item, $shortForm = '')
{
$maxLength = 80;
$tailLength = 20;
if (strlen($item) < $maxLength) {
return $item;
}
if (!empty($shortForm)) {
return $shortForm;
}
$item = trim($item);
$tail = preg_replace("#.*\n#ms", '', substr($item, -$tailLength));
$head = preg_replace("#\n.*#ms", '', substr($item, 0, $maxLength - (strlen($tail) + 5)));
return "$head ... $tail";
}
}
<?php
namespace Robo\Task;
use Robo\Result;
use Robo\Task\BaseTask;
use Robo\Contract\TaskInterface;
/**
* Extend StackBasedTask to create a Robo task that
* runs a sequence of commands.
*
* This is particularly useful for wrapping an existing
* object-oriented API. Doing it this way requires
* less code than manually adding a method for each wrapped
* function in the delegate. Additionally, wrapping the
* external class in a StackBasedTask creates a loosely-coupled
* interface -- i.e. if a new method is added to the delegate
* class, it is not necessary to update your wrapper, as the
* new functionality will be inherited.
*
* For example, you have:
*
* $frobinator = new Frobinator($a, $b, $c)
* ->friz()
* ->fraz()
* ->frob();
*
* We presume that the existing library throws an exception on error.
*
* You want:
*
* $result = $this->taskFrobinator($a, $b, $c)
* ->friz()
* ->fraz()
* ->frob()
* ->run();
*
* Execution is deferred until run(), and a Robo\Result instance is
* returned. Additionally, using Robo will covert Exceptions
* into RoboResult objects.
*
* To create a new Robo task:
*
* - Make a new class that extends StackBasedTask
* - Give it a constructor that creates a new Frobinator
* - Override getDelegate(), and return the Frobinator instance
*
* Finally, add your new class to loadTasks.php as usual,
* and you are all done.
*
* If you need to add any methods to your task that should run
* immediately (e.g. to set parameters used at run() time), just
* implement them in your derived class.
*
* If you need additional methods that should run deferred, just
* define them as 'protected function _foo()'. Then, users may
* call $this->taskFrobinator()->foo() to get deferred execution
* of _foo().
*/
abstract class StackBasedTask extends BaseTask
{
/**
* @var array
*/
protected $stack = [];
/**
* @var bool
*/
protected $stopOnFail = true;
/**
* @param bool $stop
*
* @return $this
*/
public function stopOnFail($stop = true)
{
$this->stopOnFail = $stop;
return $this;
}
/**
* Derived classes should override the getDelegate() method, and
* return an instance of the API class being wrapped. When this
* is done, any method of the delegate is available as a method of
* this class. Calling one of the delegate's methods will defer
* execution until the run() method is called.
*
* @return null
*/
protected function getDelegate()
{
return null;
}
/**
* Derived classes that have more than one delegate may override
* getCommandList to add as many delegate commands as desired to
* the list of potential functions that __call() tried to find.
*
* @param string $function
*
* @return array
*/
protected function getDelegateCommandList($function)
{
return [[$this, "_$function"], [$this->getDelegate(), $function]];
}
/**
* Print progress about the commands being executed
*
* @param string $command
* @param string $action
*/
protected function printTaskProgress($command, $action)
{
$this->printTaskInfo('{command} {action}', ['command' => "{$command[1]}", 'action' => json_encode($action)]);
}
/**
* Derived classes can override processResult to add more
* logic to result handling from functions. By default, it
* is assumed that if a function returns in int, then
* 0 == success, and any other value is the error code.
*
* @param int|\Robo\Result $function_result
*
* @return \Robo\Result
*/
protected function processResult($function_result)
{
if (is_int($function_result)) {
if ($function_result) {
return Result::error($this, $function_result);
}
}
return Result::success($this);
}
/**
* Record a function to call later.
*
* @param string $command
* @param array $args
*
* @return $this
*/
protected function addToCommandStack($command, $args)
{
$this->stack[] = array_merge([$command], $args);
return $this;
}
/**
* Any API function provided by the delegate that executes immediately
* may be handled by __call automatically. These operations will all
* be deferred until this task's run() method is called.
*
* @param string $function
* @param array $args
*
* @return $this
*/
public function __call($function, $args)
{
foreach ($this->getDelegateCommandList($function) as $command) {
if (method_exists($command[0], $command[1])) {
// Otherwise, we'll defer calling this function
// until run(), and return $this.
$this->addToCommandStack($command, $args);
return $this;
}
}
$message = "Method $function does not exist.\n";
throw new \BadMethodCallException($message);
}
/**
* @return int
*/
public function progressIndicatorSteps()
{
// run() will call advanceProgressIndicator() once for each
// file, one after calling stopBuffering, and again after compression.
return count($this->stack);
}
/**
* Run all of the queued objects on the stack
*
* @return \Robo\Result
*/
public function run()
{
$this->startProgressIndicator();
$result = Result::success($this);
foreach ($this->stack as $action) {
$command = array_shift($action);
$this->printTaskProgress($command, $action);
$this->advanceProgressIndicator();
// TODO: merge data from the result on this call
// with data from the result on the previous call?
// For now, the result always comes from the last function.
$result = $this->callTaskMethod($command, $action);
if ($this->stopOnFail && $result && !$result->wasSuccessful()) {
break;
}
}
$this->stopProgressIndicator();
// todo: add timing information to the result
return $result;
}
/**
* Execute one task method
*
* @param string $command
* @param string $action
*
* @return \Robo\Result
*/
protected function callTaskMethod($command, $action)
{
try {
$function_result = call_user_func_array($command, $action);
return $this->processResult($function_result);
} catch (\Exception $e) {
$this->printTaskError($e->getMessage());
return Result::fromException($this, $e);
}
}
}
<?php
namespace Robo\Task\Testing;
use Robo\Contract\CommandInterface;
use Robo\Contract\PrintedInterface;
use Robo\Task\BaseTask;
/**
* Runs [atoum](http://atoum.org/) tests
*
* ``` php
* <?php
* $this->taskAtoum()
* ->files('path/to/test.php')
* ->configFile('config/dev.php')
* ->run()
*
* ?>
* ```
*/
class Atoum extends BaseTask implements CommandInterface, PrintedInterface
{
use \Robo\Common\ExecOneCommand;
/**
* @var string
*/
protected $command;
/**
* Atoum constructor.
*
* @param null|string $pathToAtoum
*
* @throws \Robo\Exception\TaskException
*/
public function __construct($pathToAtoum = null)
{
$this->command = $pathToAtoum;
if (!$this->command) {
$this->command = $this->findExecutable('atoum');
}
if (!$this->command) {
throw new \Robo\Exception\TaskException(__CLASS__, "Neither local atoum nor global composer installation not found");
}
}
/**
* Tag or Tags to filter.
*
* @param string|array $tags
*
* @return $this
*/
public function tags($tags)
{
return $this->addMultipleOption('tags', $tags);
}
/**
* Display result using the light reporter.
*
* @return $this
*/
public function lightReport()
{
$this->option("--use-light-report");
return $this;
}
/**
* Display result using the tap reporter.
*
* @return $this
*/
public function tap()
{
$this->option("use-tap-report");
return $this;
}
/**
* Path to the bootstrap file.
* @param string $file
*
* @return $this
*/
public function bootstrap($file)
{
$this->option("bootstrap", $file);
return $this;
}
/**
* Path to the config file.
*
* @param string $file
*
* @return $this
*/
public function configFile($file)
{
$this->option('-c', $file);
return $this;
}
/**
* Use atoum's debug mode.
*
* @return $this
*/
public function debug()
{
$this->option("debug");
return $this;
}
/**
* Test file or test files to run.
*
* @param string|array
*
* @return $this
*/
public function files($files)
{
return $this->addMultipleOption('f', $files);
}
/**
* Test directory or directories to run.
*
* @param string|array A single directory or a list of directories.
*
* @return $this
*/
public function directories($directories)
{
return $this->addMultipleOption('directories', $directories);
}
/**
* @param string $option
* @param string|array $values
*
* @return $this
*/
protected function addMultipleOption($option, $values)
{
if (is_string($values)) {
$values = [$values];
}
foreach ($values as $value) {
$this->option($option, $value);
}
return $this;
}
/**
* {@inheritdoc}
*/
public function getCommand()
{
return $this->command . $this->arguments;
}
/**
* {@inheritdoc}
*/
public function run()
{
$this->printTaskInfo('Running atoum ' . $this->arguments);
return $this->executeCommand($this->getCommand());
}
}
<?php
namespace Robo\Task\Testing;
use Robo\Contract\CommandInterface;
use Robo\Contract\PrintedInterface;
use Robo\Task\BaseTask;
/**
* Executes Behat tests
*
* ``` php
* <?php
* $this->taskBehat()
* ->format('pretty')
* ->noInteraction()
* ->run();
* ?>
* ```
*
*/
class Behat extends BaseTask implements CommandInterface, PrintedInterface
{
use \Robo\Common\ExecOneCommand;
/**
* @var string
*/
protected $command;
/**
* @var string[] $formaters available formaters for format option
*/
protected $formaters = ['progress', 'pretty', 'junit'];
/**
* @var string[] $verbose_levels available verbose levels
*/
protected $verbose_levels = ['v', 'vv'];
/**
* Behat constructor.
*
* @param null|string $pathToBehat
*
* @throws \Robo\Exception\TaskException
*/
public function __construct($pathToBehat = null)
{
$this->command = $pathToBehat;
if (!$this->command) {
$this->command = $this->findExecutable('behat');
}
if (!$this->command) {
throw new \Robo\Exception\TaskException(__CLASS__, "Neither composer nor phar installation of Behat found");
}
$this->arg('run');
}
/**
* @return $this
*/
public function stopOnFail()
{
$this->option('stop-on-failure');
return $this;
}
/**
* @return $this
*/
public function noInteraction()
{
$this->option('no-interaction');
return $this;
}
/**
* @param $config_file
*
* @return $this
*/
public function config($config_file)
{
$this->option('config', $config_file);
return $this;
}
/**
* @return $this
*/
public function colors()
{
$this->option('colors');
return $this;
}
/**
* @return $this
*/
public function noColors()
{
$this->option('no-colors');
return $this;
}
/**
* @param string $suite
*
* @return $this
*/
public function suite($suite)
{
$this->option('suite', $suite);
return $this;
}
/**
* @param string $level
*
* @return $this
*/
public function verbose($level = 'v')
{
if (!in_array($level, $this->verbose_levels)) {
throw new \InvalidArgumentException('expected ' . implode(',', $this->verbose_levels));
}
$this->option('-' . $level);
return $this;
}
/**
* @param string $formater
*
* @return $this
*/
public function format($formater)
{
if (!in_array($formater, $this->formaters)) {
throw new \InvalidArgumentException('expected ' . implode(',', $this->formaters));
}
$this->option('format', $formater);
return $this;
}
/**
* Returns command that can be executed.
* This method is used to pass generated command from one task to another.
*
* @return string
*/
public function getCommand()
{
return $this->command . $this->arguments;
}
/**
* {@inheritdoc}
*/
public function run()
{
$this->printTaskInfo('Running behat {arguments}', ['arguments' => $this->arguments]);
return $this->executeCommand($this->getCommand());
}
}
<?php
namespace Robo\Task\Testing;
use Robo\Contract\PrintedInterface;
use Robo\Exception\TaskException;
use Robo\Task\BaseTask;
use Robo\Contract\CommandInterface;
/**
* Executes Codeception tests
*
* ``` php
* <?php
* // config
* $this->taskCodecept()
* ->suite('acceptance')
* ->env('chrome')
* ->group('admin')
* ->xml()
* ->html()
* ->run();
*
* ?>
* ```
*
*/
class Codecept extends BaseTask implements CommandInterface, PrintedInterface
{
use \Robo\Common\ExecOneCommand;
/**
* @var string
*/
protected $suite = '';
/**
* @var string
*/
protected $test = '';
/**
* @var string
*/
protected $command;
/**
* @param string $pathToCodeception
*
* @throws \Robo\Exception\TaskException
*/
public function __construct($pathToCodeception = '')
{
$this->command = $pathToCodeception;
if (!$this->command) {
$this->command = $this->findExecutable('codecept');
}
if (!$this->command) {
throw new TaskException(__CLASS__, "Neither composer nor phar installation of Codeception found.");
}
$this->command .= ' run';
}
/**
* @param string $suite
*
* @return $this
*/
public function suite($suite)
{
$this->suite = $suite;
return $this;
}
/**
* @param string $testName
*
* @return $this
*/
public function test($testName)
{
$this->test = $testName;
return $this;
}
/**
* set group option. Can be called multiple times
*
* @param string $group
*
* @return $this
*/
public function group($group)
{
$this->option("group", $group);
return $this;
}
/**
* @param string $group
*
* @return $this
*/
public function excludeGroup($group)
{
$this->option("skip-group", $group);
return $this;
}
/**
* generate json report
*
* @param string $file
*
* @return $this
*/
public function json($file = null)
{
$this->option("json", $file);
return $this;
}
/**
* generate xml JUnit report
*
* @param string $file
*
* @return $this
*/
public function xml($file = null)
{
$this->option("xml", $file);
return $this;
}
/**
* Generate html report
*
* @param string $dir
*
* @return $this
*/
public function html($dir = null)
{
$this->option("html", $dir);
return $this;
}
/**
* generate tap report
*
* @param string $file
*
* @return $this
*/
public function tap($file = null)
{
$this->option("tap", $file);
return $this;
}
/**
* provides config file other then default `codeception.yml` with `-c` option
*
* @param string $file
*
* @return $this
*/
public function configFile($file)
{
$this->option("-c", $file);
return $this;
}
/**
* collect codecoverage in raw format. You may pass name of cov file to save results
*
* @param null|string $cov
*
* @return $this
*/
public function coverage($cov = null)
{
$this->option("coverage", $cov);
return $this;
}
/**
* execute in silent mode
*
* @return $this
*/
public function silent()
{
$this->option("silent");
return $this;
}
/**
* collect code coverage in xml format. You may pass name of xml file to save results
*
* @param string $xml
*
* @return $this
*/
public function coverageXml($xml = null)
{
$this->option("coverage-xml", $xml);
return $this;
}
/**
* collect code coverage and generate html report. You may pass
*
* @param string $html
*
* @return $this
*/
public function coverageHtml($html = null)
{
$this->option("coverage-html", $html);
return $this;
}
/**
* @param string $env
*
* @return $this
*/
public function env($env)
{
$this->option("env", $env);
return $this;
}
/**
* @return $this
*/
public function debug()
{
$this->option("debug");
return $this;
}
/**
* {@inheritdoc}
*/
public function getCommand()
{
$this->option(null, $this->suite)
->option(null, $this->test);
return $this->command . $this->arguments;
}
/**
* {@inheritdoc}
*/
public function run()
{
$command = $this->getCommand();
$this->printTaskInfo('Executing {command}', ['command' => $command]);
return $this->executeCommand($command);
}
}
<?php
namespace Robo\Task\Testing;
trait loadTasks
{
/**
* @param null|string $pathToCodeception
*
* @return \Robo\Task\Testing\Codecept
*/
protected function taskCodecept($pathToCodeception = null)
{
return $this->task(Codecept::class, $pathToCodeception);
}
/**
* @param null|string $pathToPhpUnit
*
* @return \Robo\Task\Testing\PHPUnit
*/
protected function taskPhpUnit($pathToPhpUnit = null)
{
return $this->task(PHPUnit::class, $pathToPhpUnit);
}
/**
* @param null $pathToPhpspec
*
* @return \Robo\Task\Testing\Phpspec
*/
protected function taskPhpspec($pathToPhpspec = null)
{
return $this->task(Phpspec::class, $pathToPhpspec);
}
/**
* @param null $pathToAtoum
*
* @return \Robo\Task\Testing\Atoum
*/
protected function taskAtoum($pathToAtoum = null)
{
return $this->task(Atoum::class, $pathToAtoum);
}
/**
* @param null $pathToBehat
*
* @return \Robo\Task\Testing\Behat
*/
protected function taskBehat($pathToBehat = null)
{
return $this->task(Behat::class, $pathToBehat);
}
}
<?php
namespace Robo\Task\Testing;
use Robo\Contract\PrintedInterface;
use Robo\Task\BaseTask;
use Robo\Contract\CommandInterface;
/**
* Executes Phpspec tests
*
* ``` php
* <?php
* $this->taskPhpspec()
* ->format('pretty')
* ->noInteraction()
* ->run();
* ?>
* ```
*
*/
class Phpspec extends BaseTask implements CommandInterface, PrintedInterface
{
use \Robo\Common\ExecOneCommand;
/**
* @var string
*/
protected $command;
/**
* @var string[] $formaters available formaters for format option
*/
protected $formaters = ['progress', 'html', 'pretty', 'junit', 'dot', 'tap'];
/**
* @var array $verbose_levels available verbose levels
*/
protected $verbose_levels = ['v', 'vv', 'vvv'];
public function __construct($pathToPhpspec = null)
{
$this->command = $pathToPhpspec;
if (!$this->command) {
$this->command = $this->findExecutable('phpspec');
}
if (!$this->command) {
throw new \Robo\Exception\TaskException(__CLASS__, "Neither composer nor phar installation of Phpspec found");
}
$this->arg('run');
}
public function stopOnFail()
{
$this->option('stop-on-failure');
return $this;
}
public function noCodeGeneration()
{
$this->option('no-code-generation');
return $this;
}
public function quiet()
{
$this->option('quiet');
return $this;
}
public function verbose($level = 'v')
{
if (!in_array($level, $this->verbose_levels)) {
throw new \InvalidArgumentException('expected ' . implode(',', $this->verbose_levels));
}
$this->option('-' . $level);
return $this;
}
public function noAnsi()
{
$this->option('no-ansi');
return $this;
}
public function noInteraction()
{
$this->option('no-interaction');
return $this;
}
public function config($config_file)
{
$this->option('config', $config_file);
return $this;
}
public function format($formater)
{
if (!in_array($formater, $this->formaters)) {
throw new \InvalidArgumentException('expected ' . implode(',', $this->formaters));
}
$this->option('format', $formater);
return $this;
}
public function getCommand()
{
return $this->command . $this->arguments;
}
public function run()
{
$this->printTaskInfo('Running phpspec {arguments}', ['arguments' => $this->arguments]);
return $this->executeCommand($this->getCommand());
}
}
<?php
namespace Robo\Task\Testing;
use Robo\Contract\CommandInterface;
use Robo\Contract\PrintedInterface;
use Robo\Task\BaseTask;
/**
* Runs PHPUnit tests
*
* ``` php
* <?php
* $this->taskPHPUnit()
* ->group('core')
* ->bootstrap('test/bootstrap.php')
* ->run()
*
* ?>
* ```
*/
class PHPUnit extends BaseTask implements CommandInterface, PrintedInterface
{
use \Robo\Common\ExecOneCommand;
/**
* @var string
*/
protected $command;
/**
* Directory of test files or single test file to run. Appended to
* the command and arguments.
*
* @var string
*/
protected $files = '';
public function __construct($pathToPhpUnit = null)
{
$this->command = $pathToPhpUnit;
if (!$this->command) {
$this->command = $this->findExecutablePhar('phpunit');
}
if (!$this->command) {
throw new \Robo\Exception\TaskException(__CLASS__, "Neither local phpunit nor global composer installation not found");
}
}
/**
* @param string $filter
*
* @return $this
*/
public function filter($filter)
{
$this->option('filter', $filter);
return $this;
}
/**
* @param string $group
*
* @return $this
*/
public function group($group)
{
$this->option("group", $group);
return $this;
}
/**
* @param string $group
*
* @return $this
*/
public function excludeGroup($group)
{
$this->option("exclude-group", $group);
return $this;
}
/**
* adds `log-json` option to runner
*
* @param string $file
*
* @return $this
*/
public function json($file = null)
{
$this->option("log-json", $file);
return $this;
}
/**
* adds `log-junit` option
*
* @param string $file
*
* @return $this
*/
public function xml($file = null)
{
$this->option("log-junit", $file);
return $this;
}
/**
* @param string $file
*
* @return $this
*/
public function tap($file = "")
{
$this->option("log-tap", $file);
return $this;
}
/**
* @param string $file
*
* @return $this
*/
public function bootstrap($file)
{
$this->option("bootstrap", $file);
return $this;
}
/**
* @param string $file
*
* @return $this
*/
public function configFile($file)
{
$this->option('-c', $file);
return $this;
}
/**
* @return $this
*/
public function debug()
{
$this->option("debug");
return $this;
}
/**
* Directory of test files or single test file to run.
*
* @param string $files A single test file or a directory containing test files.
*
* @return $this
*
* @throws \Robo\Exception\TaskException
*
* @deprecated Use file() or dir() method instead
*/
public function files($files)
{
if (!empty($this->files) || is_array($files)) {
throw new \Robo\Exception\TaskException(__CLASS__, "Only one file or directory may be provided.");
}
$this->files = ' ' . $files;
return $this;
}
/**
* Test the provided file.
*
* @param string $file path to file to test
*
* @return $this
*/
public function file($file)
{
return $this->files($file);
}
/**
* {@inheritdoc}
*/
public function getCommand()
{
return $this->command . $this->arguments . $this->files;
}
/**
* {@inheritdoc}
*/
public function run()
{
$this->printTaskInfo('Running PHPUnit {arguments}', ['arguments' => $this->arguments]);
return $this->executeCommand($this->getCommand());
}
}
<?php
namespace Robo\Task\Vcs;
use Robo\Task\CommandStack;
use Symfony\Component\Process\ProcessUtils;
/**
* Runs Git commands in stack. You can use `stopOnFail()` to point that stack should be terminated on first fail.
*
* ``` php
* <?php
* $this->taskGitStack()
* ->stopOnFail()
* ->add('-A')
* ->commit('adding everything')
* ->push('origin','master')
* ->tag('0.6.0')
* ->push('origin','0.6.0')
* ->run()
*
* $this->taskGitStack()
* ->stopOnFail()
* ->add('doc/*')
* ->commit('doc updated')
* ->push()
* ->run();
* ?>
* ```
*/
class GitStack extends CommandStack
{
/**
* @param string $pathToGit
*/
public function __construct($pathToGit = 'git')
{
$this->executable = $pathToGit;
}
/**
* Executes `git clone`
*
* @param string $repo
* @param string $to
*
* @return $this
*/
public function cloneRepo($repo, $to = "")
{
return $this->exec(['clone', $repo, $to]);
}
/**
* Executes `git add` command with files to add pattern
*
* @param string $pattern
*
* @return $this
*/
public function add($pattern)
{
return $this->exec([__FUNCTION__, $pattern]);
}
/**
* Executes `git commit` command with a message
*
* @param string $message
* @param string $options
*
* @return $this
*/
public function commit($message, $options = "")
{
$message = ProcessUtils::escapeArgument($message);
return $this->exec([__FUNCTION__, "-m $message", $options]);
}
/**
* Executes `git pull` command.
*
* @param string $origin
* @param string $branch
*
* @return $this
*/
public function pull($origin = '', $branch = '')
{
return $this->exec([__FUNCTION__, $origin, $branch]);
}
/**
* Executes `git push` command
*
* @param string $origin
* @param string $branch
*
* @return $this
*/
public function push($origin = '', $branch = '')
{
return $this->exec([__FUNCTION__, $origin, $branch]);
}
/**
* Performs git merge
*
* @param string $branch
*
* @return $this
*/
public function merge($branch)
{
return $this->exec([__FUNCTION__, $branch]);
}
/**
* Executes `git checkout` command
*
* @param string $branch
*
* @return $this
*/
public function checkout($branch)
{
return $this->exec([__FUNCTION__, $branch]);
}
/**
* Executes `git tag` command
*
* @param string $tag_name
* @param string $message
*
* @return $this
*/
public function tag($tag_name, $message = "")
{
if ($message != "") {
$message = "-m '$message'";
}
return $this->exec([__FUNCTION__, $message, $tag_name]);
}
/**
* {@inheritdoc}
*/
public function run()
{
$this->printTaskInfo("Running git commands...");
return parent::run();
}
}
<?php
namespace Robo\Task\Vcs;
use Robo\Task\CommandStack;
/**
* Runs hg commands in stack. You can use `stopOnFail()` to point that stack should be terminated on first fail.
*
* ``` php
* <?php
* $this->hgStack
* ->cloneRepo('https://bitbucket.org/durin42/hgsubversion')
* ->pull()
* ->add()
* ->commit('changed')
* ->push()
* ->tag('0.6.0')
* ->push('0.6.0')
* ->run();
* ?>
* ```
*/
class HgStack extends CommandStack
{
/**
* @param string $pathToHg
*/
public function __construct($pathToHg = 'hg')
{
$this->executable = $pathToHg;
}
/**
* Executes `hg clone`
*
* @param string $repo
* @param string $to
*
* @return $this
*/
public function cloneRepo($repo, $to = '')
{
return $this->exec(['clone', $repo, $to]);
}
/**
* Executes `hg add` command with files to add by pattern
*
* @param string $include
* @param string $exclude
*
* @return $this
*/
public function add($include = '', $exclude = '')
{
if (strlen($include) > 0) {
$include = "-I {$include}";
}
if (strlen($exclude) > 0) {
$exclude = "-X {$exclude}";
}
return $this->exec([__FUNCTION__, $include, $exclude]);
}
/**
* Executes `hg commit` command with a message
*
* @param string $message
* @param string $options
*
* @return $this
*/
public function commit($message, $options = '')
{
return $this->exec([__FUNCTION__, "-m '{$message}'", $options]);
}
/**
* Executes `hg pull` command.
*
* @param string $branch
*
* @return $this
*/
public function pull($branch = '')
{
if (strlen($branch) > 0) {
$branch = "-b '{$branch}''";
}
return $this->exec([__FUNCTION__, $branch]);
}
/**
* Executes `hg push` command
*
* @param string $branch
*
* @return $this
*/
public function push($branch = '')
{
if (strlen($branch) > 0) {
$branch = "-b '{$branch}'";
}
return $this->exec([__FUNCTION__, $branch]);
}
/**
* Performs hg merge
*
* @param string $revision
*
* @return $this
*/
public function merge($revision = '')
{
if (strlen($revision) > 0) {
$revision = "-r {$revision}";
}
return $this->exec([__FUNCTION__, $revision]);
}
/**
* Executes `hg tag` command
*
* @param string $tag_name
* @param string $message
*
* @return $this
*/
public function tag($tag_name, $message = '')
{
if ($message !== '') {
$message = "-m '{$message}'";
}
return $this->exec([__FUNCTION__, $message, $tag_name]);
}
/**
* {@inheritdoc}
*/
public function run()
{
$this->printTaskInfo('Running hg commands...');
return parent::run();
}
}
<?php
namespace Robo\Task\Vcs;
trait loadShortcuts
{
/**
* @param string $url
*
* @return \Robo\Result
*/
protected function _svnCheckout($url)
{
return $this->taskSvnStack()->checkout($url)->run();
}
/**
* @param string $url
*
* @return \Robo\Result
*/
protected function _gitClone($url)
{
return $this->taskGitStack()->cloneRepo($url)->run();
}
/**
* @param string $url
*
* @return \Robo\Result
*/
protected function _hgClone($url)
{
return $this->taskHgStack()->cloneRepo($url)->run();
}
}
<?php
namespace Robo\Task\Vcs;
trait loadTasks
{
/**
* @param string $username
* @param string $password
* @param string $pathToSvn
*
* @return \Robo\Task\Vcs\SvnStack
*/
protected function taskSvnStack($username = '', $password = '', $pathToSvn = 'svn')
{
return $this->task(SvnStack::class, $username, $password, $pathToSvn);
}
/**
* @param string $pathToGit
*
* @return \Robo\Task\Vcs\GitStack
*/
protected function taskGitStack($pathToGit = 'git')
{
return $this->task(GitStack::class, $pathToGit);
}
/**
* @param string $pathToHg
*
* @return \Robo\Task\Vcs\HgStack
*/
protected function taskHgStack($pathToHg = 'hg')
{
return $this->task(HgStack::class, $pathToHg);
}
}
<?php
namespace Robo\Task\Vcs;
use Robo\Contract\CommandInterface;
use Robo\Result;
use Robo\Task\CommandStack;
/**
* Runs Svn commands in stack. You can use `stopOnFail()` to point that stack should be terminated on first fail.
*
* ``` php
* <?php
* $this->taskSvnStack()
* ->checkout('http://svn.collab.net/repos/svn/trunk')
* ->run()
*
* // alternatively
* $this->_svnCheckout('http://svn.collab.net/repos/svn/trunk');
*
* $this->taskSvnStack('username', 'password')
* ->stopOnFail()
* ->update()
* ->add('doc/*')
* ->commit('doc updated')
* ->run();
* ?>
* ```
*/
class SvnStack extends CommandStack implements CommandInterface
{
/**
* @var bool
*/
protected $stopOnFail = false;
/**
* @var \Robo\Result
*/
protected $result;
/**
* @param string $username
* @param string $password
* @param string $pathToSvn
*/
public function __construct($username = '', $password = '', $pathToSvn = 'svn')
{
$this->executable = $pathToSvn;
if (!empty($username)) {
$this->executable .= " --username $username";
}
if (!empty($password)) {
$this->executable .= " --password $password";
}
$this->result = Result::success($this);
}
/**
* Updates `svn update` command
*
* @param string $path
*
* @return $this;
*/
public function update($path = '')
{
return $this->exec("update $path");
}
/**
* Executes `svn add` command with files to add pattern
*
* @param string $pattern
*
* @return $this
*/
public function add($pattern = '')
{
return $this->exec("add $pattern");
}
/**
* Executes `svn commit` command with a message
*
* @param string $message
* @param string $options
*
* @return $this
*/
public function commit($message, $options = "")
{
return $this->exec("commit -m '$message' $options");
}
/**
* Executes `svn checkout` command
*
* @param string $branch
*
* @return $this
*/
public function checkout($branch)
{
return $this->exec("checkout $branch");
}
}
<?php
namespace Robo;
use Robo\Common\BuilderAwareTrait;
trait TaskAccessor
{
use BuilderAwareTrait;
/**
* Provides the collection builder with access to all of the
* protected 'task' methods available on this object.
*
* @param string $fn
* @param array $args
*
* @return null|\Robo\Collection\CollectionBuilder
*/
public function getBuiltTask($fn, $args)
{
if (preg_match('#^task[A-Z]#', $fn)) {
return call_user_func_array([$this, $fn], $args);
}
}
/**
* Alternative access to instantiate. Use:
*
* $this->task(Foo::class, $a, $b);
*
* instead of:
*
* $this->taskFoo($a, $b);
*
* The later form is preferred.
*
* @return \Robo\Collection\CollectionBuilder
*/
protected function task()
{
$args = func_get_args();
$name = array_shift($args);
$collectionBuilder = $this->collectionBuilder();
return $collectionBuilder->build($name, $args);
}
}
<?php
namespace Robo;
class TaskInfo
{
/**
* Return a context useful for logging messages.
*
* @param object $task
*
* @return array
*/
public static function getTaskContext($task)
{
return [
'name' => TaskInfo::formatTaskName($task),
'task' => $task,
];
}
/**
* @param object $task
*
* @return string
*/
public static function formatTaskName($task)
{
$name = get_class($task);
$name = preg_replace('~Stack^~', '', $name);
$name = str_replace('Robo\\Task\Base\\', '', $name);
$name = str_replace('Robo\\Task\\', '', $name);
$name = str_replace('Robo\\Collection\\', '', $name);
return $name;
}
}
<?php
namespace Robo;
use Robo\Common\IO;
use Robo\Contract\IOAwareInterface;
use Robo\Contract\BuilderAwareInterface;
use League\Container\ContainerAwareInterface;
use League\Container\ContainerAwareTrait;
class Tasks implements BuilderAwareInterface, IOAwareInterface, ContainerAwareInterface
{
use ContainerAwareTrait;
use LoadAllTasks; // uses TaskAccessor, which uses BuilderAwareTrait
use IO;
/**
* @param bool $stopOnFail
*/
protected function stopOnFail($stopOnFail = true)
{
Result::$stopOnFail = $stopOnFail;
}
}
<?php
// autoload.php @generated by Composer
require_once __DIR__ . '/composer' . '/autoload_real.php';
return ComposerAutoloaderInitecf10dbf6fe321b2c8e9780228146676::getLoader();
<?php
// autoload_classmap.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
'CssAtCharsetParserPlugin' => $vendorDir . '/natxet/CssMin/src/CssMin.php',
'CssAtCharsetToken' => $vendorDir . '/natxet/CssMin/src/CssMin.php',
'CssAtFontFaceDeclarationToken' => $vendorDir . '/natxet/CssMin/src/CssMin.php',
'CssAtFontFaceEndToken' => $vendorDir . '/natxet/CssMin/src/CssMin.php',
'CssAtFontFaceParserPlugin' => $vendorDir . '/natxet/CssMin/src/CssMin.php',
'CssAtFontFaceStartToken' => $vendorDir . '/natxet/CssMin/src/CssMin.php',
'CssAtImportParserPlugin' => $vendorDir . '/natxet/CssMin/src/CssMin.php',
'CssAtImportToken' => $vendorDir . '/natxet/CssMin/src/CssMin.php',
'CssAtKeyframesEndToken' => $vendorDir . '/natxet/CssMin/src/CssMin.php',
'CssAtKeyframesParserPlugin' => $vendorDir . '/natxet/CssMin/src/CssMin.php',
'CssAtKeyframesRulesetDeclarationToken' => $vendorDir . '/natxet/CssMin/src/CssMin.php',
'CssAtKeyframesRulesetEndToken' => $vendorDir . '/natxet/CssMin/src/CssMin.php',
'CssAtKeyframesRulesetStartToken' => $vendorDir . '/natxet/CssMin/src/CssMin.php',
'CssAtKeyframesStartToken' => $vendorDir . '/natxet/CssMin/src/CssMin.php',
'CssAtMediaEndToken' => $vendorDir . '/natxet/CssMin/src/CssMin.php',
'CssAtMediaParserPlugin' => $vendorDir . '/natxet/CssMin/src/CssMin.php',
'CssAtMediaStartToken' => $vendorDir . '/natxet/CssMin/src/CssMin.php',
'CssAtPageDeclarationToken' => $vendorDir . '/natxet/CssMin/src/CssMin.php',
'CssAtPageEndToken' => $vendorDir . '/natxet/CssMin/src/CssMin.php',
'CssAtPageParserPlugin' => $vendorDir . '/natxet/CssMin/src/CssMin.php',
'CssAtPageStartToken' => $vendorDir . '/natxet/CssMin/src/CssMin.php',
'CssAtVariablesDeclarationToken' => $vendorDir . '/natxet/CssMin/src/CssMin.php',
'CssAtVariablesEndToken' => $vendorDir . '/natxet/CssMin/src/CssMin.php',
'CssAtVariablesParserPlugin' => $vendorDir . '/natxet/CssMin/src/CssMin.php',
'CssAtVariablesStartToken' => $vendorDir . '/natxet/CssMin/src/CssMin.php',
'CssCommentParserPlugin' => $vendorDir . '/natxet/CssMin/src/CssMin.php',
'CssCommentToken' => $vendorDir . '/natxet/CssMin/src/CssMin.php',
'CssCompressColorValuesMinifierPlugin' => $vendorDir . '/natxet/CssMin/src/CssMin.php',
'CssCompressExpressionValuesMinifierPlugin' => $vendorDir . '/natxet/CssMin/src/CssMin.php',
'CssCompressUnitValuesMinifierPlugin' => $vendorDir . '/natxet/CssMin/src/CssMin.php',
'CssConvertFontWeightMinifierPlugin' => $vendorDir . '/natxet/CssMin/src/CssMin.php',
'CssConvertHslColorsMinifierPlugin' => $vendorDir . '/natxet/CssMin/src/CssMin.php',
'CssConvertLevel3AtKeyframesMinifierFilter' => $vendorDir . '/natxet/CssMin/src/CssMin.php',
'CssConvertLevel3PropertiesMinifierFilter' => $vendorDir . '/natxet/CssMin/src/CssMin.php',
'CssConvertNamedColorsMinifierPlugin' => $vendorDir . '/natxet/CssMin/src/CssMin.php',
'CssConvertRgbColorsMinifierPlugin' => $vendorDir . '/natxet/CssMin/src/CssMin.php',
'CssError' => $vendorDir . '/natxet/CssMin/src/CssMin.php',
'CssExpressionParserPlugin' => $vendorDir . '/natxet/CssMin/src/CssMin.php',
'CssImportImportsMinifierFilter' => $vendorDir . '/natxet/CssMin/src/CssMin.php',
'CssMin' => $vendorDir . '/natxet/CssMin/src/CssMin.php',
'CssMinifier' => $vendorDir . '/natxet/CssMin/src/CssMin.php',
'CssNullToken' => $vendorDir . '/natxet/CssMin/src/CssMin.php',
'CssOtbsFormatter' => $vendorDir . '/natxet/CssMin/src/CssMin.php',
'CssParser' => $vendorDir . '/natxet/CssMin/src/CssMin.php',
'CssRemoveCommentsMinifierFilter' => $vendorDir . '/natxet/CssMin/src/CssMin.php',
'CssRemoveEmptyAtBlocksMinifierFilter' => $vendorDir . '/natxet/CssMin/src/CssMin.php',
'CssRemoveEmptyRulesetsMinifierFilter' => $vendorDir . '/natxet/CssMin/src/CssMin.php',
'CssRemoveLastDelarationSemiColonMinifierFilter' => $vendorDir . '/natxet/CssMin/src/CssMin.php',
'CssRulesetDeclarationToken' => $vendorDir . '/natxet/CssMin/src/CssMin.php',
'CssRulesetEndToken' => $vendorDir . '/natxet/CssMin/src/CssMin.php',
'CssRulesetParserPlugin' => $vendorDir . '/natxet/CssMin/src/CssMin.php',
'CssRulesetStartToken' => $vendorDir . '/natxet/CssMin/src/CssMin.php',
'CssSortRulesetPropertiesMinifierFilter' => $vendorDir . '/natxet/CssMin/src/CssMin.php',
'CssStringParserPlugin' => $vendorDir . '/natxet/CssMin/src/CssMin.php',
'CssUrlParserPlugin' => $vendorDir . '/natxet/CssMin/src/CssMin.php',
'CssVariablesMinifierFilter' => $vendorDir . '/natxet/CssMin/src/CssMin.php',
'CssVariablesMinifierPlugin' => $vendorDir . '/natxet/CssMin/src/CssMin.php',
'CssWhitesmithsFormatter' => $vendorDir . '/natxet/CssMin/src/CssMin.php',
'Robo\\composer\\ScriptHandler' => $baseDir . '/scripts/composer/ScriptHandler.php',
'aCssAtBlockEndToken' => $vendorDir . '/natxet/CssMin/src/CssMin.php',
'aCssAtBlockStartToken' => $vendorDir . '/natxet/CssMin/src/CssMin.php',
'aCssDeclarationToken' => $vendorDir . '/natxet/CssMin/src/CssMin.php',
'aCssFormatter' => $vendorDir . '/natxet/CssMin/src/CssMin.php',
'aCssMinifierFilter' => $vendorDir . '/natxet/CssMin/src/CssMin.php',
'aCssMinifierPlugin' => $vendorDir . '/natxet/CssMin/src/CssMin.php',
'aCssParserPlugin' => $vendorDir . '/natxet/CssMin/src/CssMin.php',
'aCssRulesetEndToken' => $vendorDir . '/natxet/CssMin/src/CssMin.php',
'aCssRulesetStartToken' => $vendorDir . '/natxet/CssMin/src/CssMin.php',
'aCssToken' => $vendorDir . '/natxet/CssMin/src/CssMin.php',
);
<?php
// autoload_files.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php',
);
<?php
// autoload_namespaces.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
'PHPDocsMD' => array($vendorDir . '/victorjonsson/markdowndocs/src'),
'PEAR' => array($vendorDir . '/pear/pear_exception'),
'Lurker' => array($vendorDir . '/henrikbjorn/lurker/src'),
'Console' => array($vendorDir . '/pear/console_getopt'),
'Archive_Tar' => array($vendorDir . '/pear/archive_tar'),
'' => array($vendorDir . '/pear/pear-core-minimal/src'),
);
<?php
// autoload_psr4.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
'phpDocumentor\\Reflection\\' => array($vendorDir . '/phpdocumentor/reflection-common/src', $vendorDir . '/phpdocumentor/type-resolver/src', $vendorDir . '/phpdocumentor/reflection-docblock/src'),
'Webmozart\\Assert\\' => array($vendorDir . '/webmozart/assert/src'),
'Symfony\\Polyfill\\Mbstring\\' => array($vendorDir . '/symfony/polyfill-mbstring'),
'Symfony\\Component\\Process\\' => array($vendorDir . '/symfony/process'),
'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'),
'Symfony\\Component\\Config\\' => array($vendorDir . '/symfony/config'),
'Robo\\' => array($baseDir . '/src', $baseDir . '/tests/src'),
'Psr\\Log\\' => array($vendorDir . '/psr/log/Psr/Log'),
'Patchwork\\' => array($vendorDir . '/patchwork/jsqueeze/src'),
'League\\Container\\' => array($vendorDir . '/league/container/src'),
'Interop\\Container\\' => array($vendorDir . '/container-interop/container-interop/src/Interop/Container'),
'Consolidation\\OutputFormatters\\' => array($vendorDir . '/consolidation/output-formatters/src'),
'Consolidation\\Log\\' => array($vendorDir . '/consolidation/log/src'),
'Consolidation\\AnnotatedCommand\\' => array($vendorDir . '/consolidation/annotated-command/src'),
);
<?php
// autoload_real.php @generated by Composer
class ComposerAutoloaderInitecf10dbf6fe321b2c8e9780228146676
{
private static $loader;
public static function loadClassLoader($class)
{
if ('Composer\Autoload\ClassLoader' === $class) {
require __DIR__ . '/ClassLoader.php';
}
}
public static function getLoader()
{
if (null !== self::$loader) {
return self::$loader;
}
spl_autoload_register(array('ComposerAutoloaderInitecf10dbf6fe321b2c8e9780228146676', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader();
spl_autoload_unregister(array('ComposerAutoloaderInitecf10dbf6fe321b2c8e9780228146676', 'loadClassLoader'));
$includePaths = require __DIR__ . '/include_paths.php';
array_push($includePaths, get_include_path());
set_include_path(join(PATH_SEPARATOR, $includePaths));
$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION');
if ($useStaticLoader) {
require_once __DIR__ . '/autoload_static.php';
call_user_func(\Composer\Autoload\ComposerStaticInitecf10dbf6fe321b2c8e9780228146676::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\ComposerStaticInitecf10dbf6fe321b2c8e9780228146676::$files;
} else {
$includeFiles = require __DIR__ . '/autoload_files.php';
}
foreach ($includeFiles as $fileIdentifier => $file) {
composerRequireecf10dbf6fe321b2c8e9780228146676($fileIdentifier, $file);
}
return $loader;
}
}
function composerRequireecf10dbf6fe321b2c8e9780228146676($fileIdentifier, $file)
{
if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
require $file;
$GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
}
}
<?php
// autoload_static.php @generated by Composer
namespace Composer\Autoload;
class ComposerStaticInitecf10dbf6fe321b2c8e9780228146676
{
public static $files = array (
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php',
);
public static $prefixLengthsPsr4 = array (
'p' =>
array (
'phpDocumentor\\Reflection\\' => 25,
),
'W' =>
array (
'Webmozart\\Assert\\' => 17,
),
'S' =>
array (
'Symfony\\Polyfill\\Mbstring\\' => 26,
'Symfony\\Component\\Process\\' => 26,
'Symfony\\Component\\Finder\\' => 25,
'Symfony\\Component\\Filesystem\\' => 29,
'Symfony\\Component\\EventDispatcher\\' => 34,
'Symfony\\Component\\Debug\\' => 24,
'Symfony\\Component\\Console\\' => 26,
'Symfony\\Component\\Config\\' => 25,
),
'R' =>
array (
'Robo\\' => 5,
),
'P' =>
array (
'Psr\\Log\\' => 8,
'Patchwork\\' => 10,
),
'L' =>
array (
'League\\Container\\' => 17,
),
'I' =>
array (
'Interop\\Container\\' => 18,
),
'C' =>
array (
'Consolidation\\OutputFormatters\\' => 31,
'Consolidation\\Log\\' => 18,
'Consolidation\\AnnotatedCommand\\' => 31,
),
);
public static $prefixDirsPsr4 = array (
'phpDocumentor\\Reflection\\' =>
array (
0 => __DIR__ . '/..' . '/phpdocumentor/reflection-common/src',
1 => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src',
2 => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src',
),
'Webmozart\\Assert\\' =>
array (
0 => __DIR__ . '/..' . '/webmozart/assert/src',
),
'Symfony\\Polyfill\\Mbstring\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring',
),
'Symfony\\Component\\Process\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/process',
),
'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',
),
'Symfony\\Component\\Config\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/config',
),
'Robo\\' =>
array (
0 => __DIR__ . '/../..' . '/src',
1 => __DIR__ . '/../..' . '/tests/src',
),
'Psr\\Log\\' =>
array (
0 => __DIR__ . '/..' . '/psr/log/Psr/Log',
),
'Patchwork\\' =>
array (
0 => __DIR__ . '/..' . '/patchwork/jsqueeze/src',
),
'League\\Container\\' =>
array (
0 => __DIR__ . '/..' . '/league/container/src',
),
'Interop\\Container\\' =>
array (
0 => __DIR__ . '/..' . '/container-interop/container-interop/src/Interop/Container',
),
'Consolidation\\OutputFormatters\\' =>
array (
0 => __DIR__ . '/..' . '/consolidation/output-formatters/src',
),
'Consolidation\\Log\\' =>
array (
0 => __DIR__ . '/..' . '/consolidation/log/src',
),
'Consolidation\\AnnotatedCommand\\' =>
array (
0 => __DIR__ . '/..' . '/consolidation/annotated-command/src',
),
);
public static $prefixesPsr0 = array (
'P' =>
array (
'PHPDocsMD' =>
array (
0 => __DIR__ . '/..' . '/victorjonsson/markdowndocs/src',
),
'PEAR' =>
array (
0 => __DIR__ . '/..' . '/pear/pear_exception',
),
),
'L' =>
array (
'Lurker' =>
array (
0 => __DIR__ . '/..' . '/henrikbjorn/lurker/src',
),
),
'C' =>
array (
'Console' =>
array (
0 => __DIR__ . '/..' . '/pear/console_getopt',
),
),
'A' =>
array (
'Archive_Tar' =>
array (
0 => __DIR__ . '/..' . '/pear/archive_tar',
),
),
);
public static $fallbackDirsPsr0 = array (
0 => __DIR__ . '/..' . '/pear/pear-core-minimal/src',
);
public static $classMap = array (
'CssAtCharsetParserPlugin' => __DIR__ . '/..' . '/natxet/CssMin/src/CssMin.php',
'CssAtCharsetToken' => __DIR__ . '/..' . '/natxet/CssMin/src/CssMin.php',
'CssAtFontFaceDeclarationToken' => __DIR__ . '/..' . '/natxet/CssMin/src/CssMin.php',
'CssAtFontFaceEndToken' => __DIR__ . '/..' . '/natxet/CssMin/src/CssMin.php',
'CssAtFontFaceParserPlugin' => __DIR__ . '/..' . '/natxet/CssMin/src/CssMin.php',
'CssAtFontFaceStartToken' => __DIR__ . '/..' . '/natxet/CssMin/src/CssMin.php',
'CssAtImportParserPlugin' => __DIR__ . '/..' . '/natxet/CssMin/src/CssMin.php',
'CssAtImportToken' => __DIR__ . '/..' . '/natxet/CssMin/src/CssMin.php',
'CssAtKeyframesEndToken' => __DIR__ . '/..' . '/natxet/CssMin/src/CssMin.php',
'CssAtKeyframesParserPlugin' => __DIR__ . '/..' . '/natxet/CssMin/src/CssMin.php',
'CssAtKeyframesRulesetDeclarationToken' => __DIR__ . '/..' . '/natxet/CssMin/src/CssMin.php',
'CssAtKeyframesRulesetEndToken' => __DIR__ . '/..' . '/natxet/CssMin/src/CssMin.php',
'CssAtKeyframesRulesetStartToken' => __DIR__ . '/..' . '/natxet/CssMin/src/CssMin.php',
'CssAtKeyframesStartToken' => __DIR__ . '/..' . '/natxet/CssMin/src/CssMin.php',
'CssAtMediaEndToken' => __DIR__ . '/..' . '/natxet/CssMin/src/CssMin.php',
'CssAtMediaParserPlugin' => __DIR__ . '/..' . '/natxet/CssMin/src/CssMin.php',
'CssAtMediaStartToken' => __DIR__ . '/..' . '/natxet/CssMin/src/CssMin.php',
'CssAtPageDeclarationToken' => __DIR__ . '/..' . '/natxet/CssMin/src/CssMin.php',
'CssAtPageEndToken' => __DIR__ . '/..' . '/natxet/CssMin/src/CssMin.php',
'CssAtPageParserPlugin' => __DIR__ . '/..' . '/natxet/CssMin/src/CssMin.php',
'CssAtPageStartToken' => __DIR__ . '/..' . '/natxet/CssMin/src/CssMin.php',
'CssAtVariablesDeclarationToken' => __DIR__ . '/..' . '/natxet/CssMin/src/CssMin.php',
'CssAtVariablesEndToken' => __DIR__ . '/..' . '/natxet/CssMin/src/CssMin.php',
'CssAtVariablesParserPlugin' => __DIR__ . '/..' . '/natxet/CssMin/src/CssMin.php',
'CssAtVariablesStartToken' => __DIR__ . '/..' . '/natxet/CssMin/src/CssMin.php',
'CssCommentParserPlugin' => __DIR__ . '/..' . '/natxet/CssMin/src/CssMin.php',
'CssCommentToken' => __DIR__ . '/..' . '/natxet/CssMin/src/CssMin.php',
'CssCompressColorValuesMinifierPlugin' => __DIR__ . '/..' . '/natxet/CssMin/src/CssMin.php',
'CssCompressExpressionValuesMinifierPlugin' => __DIR__ . '/..' . '/natxet/CssMin/src/CssMin.php',
'CssCompressUnitValuesMinifierPlugin' => __DIR__ . '/..' . '/natxet/CssMin/src/CssMin.php',
'CssConvertFontWeightMinifierPlugin' => __DIR__ . '/..' . '/natxet/CssMin/src/CssMin.php',
'CssConvertHslColorsMinifierPlugin' => __DIR__ . '/..' . '/natxet/CssMin/src/CssMin.php',
'CssConvertLevel3AtKeyframesMinifierFilter' => __DIR__ . '/..' . '/natxet/CssMin/src/CssMin.php',
'CssConvertLevel3PropertiesMinifierFilter' => __DIR__ . '/..' . '/natxet/CssMin/src/CssMin.php',
'CssConvertNamedColorsMinifierPlugin' => __DIR__ . '/..' . '/natxet/CssMin/src/CssMin.php',
'CssConvertRgbColorsMinifierPlugin' => __DIR__ . '/..' . '/natxet/CssMin/src/CssMin.php',
'CssError' => __DIR__ . '/..' . '/natxet/CssMin/src/CssMin.php',
'CssExpressionParserPlugin' => __DIR__ . '/..' . '/natxet/CssMin/src/CssMin.php',
'CssImportImportsMinifierFilter' => __DIR__ . '/..' . '/natxet/CssMin/src/CssMin.php',
'CssMin' => __DIR__ . '/..' . '/natxet/CssMin/src/CssMin.php',
'CssMinifier' => __DIR__ . '/..' . '/natxet/CssMin/src/CssMin.php',
'CssNullToken' => __DIR__ . '/..' . '/natxet/CssMin/src/CssMin.php',
'CssOtbsFormatter' => __DIR__ . '/..' . '/natxet/CssMin/src/CssMin.php',
'CssParser' => __DIR__ . '/..' . '/natxet/CssMin/src/CssMin.php',
'CssRemoveCommentsMinifierFilter' => __DIR__ . '/..' . '/natxet/CssMin/src/CssMin.php',
'CssRemoveEmptyAtBlocksMinifierFilter' => __DIR__ . '/..' . '/natxet/CssMin/src/CssMin.php',
'CssRemoveEmptyRulesetsMinifierFilter' => __DIR__ . '/..' . '/natxet/CssMin/src/CssMin.php',
'CssRemoveLastDelarationSemiColonMinifierFilter' => __DIR__ . '/..' . '/natxet/CssMin/src/CssMin.php',
'CssRulesetDeclarationToken' => __DIR__ . '/..' . '/natxet/CssMin/src/CssMin.php',
'CssRulesetEndToken' => __DIR__ . '/..' . '/natxet/CssMin/src/CssMin.php',
'CssRulesetParserPlugin' => __DIR__ . '/..' . '/natxet/CssMin/src/CssMin.php',
'CssRulesetStartToken' => __DIR__ . '/..' . '/natxet/CssMin/src/CssMin.php',
'CssSortRulesetPropertiesMinifierFilter' => __DIR__ . '/..' . '/natxet/CssMin/src/CssMin.php',
'CssStringParserPlugin' => __DIR__ . '/..' . '/natxet/CssMin/src/CssMin.php',
'CssUrlParserPlugin' => __DIR__ . '/..' . '/natxet/CssMin/src/CssMin.php',
'CssVariablesMinifierFilter' => __DIR__ . '/..' . '/natxet/CssMin/src/CssMin.php',
'CssVariablesMinifierPlugin' => __DIR__ . '/..' . '/natxet/CssMin/src/CssMin.php',
'CssWhitesmithsFormatter' => __DIR__ . '/..' . '/natxet/CssMin/src/CssMin.php',
'Robo\\composer\\ScriptHandler' => __DIR__ . '/../..' . '/scripts/composer/ScriptHandler.php',
'aCssAtBlockEndToken' => __DIR__ . '/..' . '/natxet/CssMin/src/CssMin.php',
'aCssAtBlockStartToken' => __DIR__ . '/..' . '/natxet/CssMin/src/CssMin.php',
'aCssDeclarationToken' => __DIR__ . '/..' . '/natxet/CssMin/src/CssMin.php',
'aCssFormatter' => __DIR__ . '/..' . '/natxet/CssMin/src/CssMin.php',
'aCssMinifierFilter' => __DIR__ . '/..' . '/natxet/CssMin/src/CssMin.php',
'aCssMinifierPlugin' => __DIR__ . '/..' . '/natxet/CssMin/src/CssMin.php',
'aCssParserPlugin' => __DIR__ . '/..' . '/natxet/CssMin/src/CssMin.php',
'aCssRulesetEndToken' => __DIR__ . '/..' . '/natxet/CssMin/src/CssMin.php',
'aCssRulesetStartToken' => __DIR__ . '/..' . '/natxet/CssMin/src/CssMin.php',
'aCssToken' => __DIR__ . '/..' . '/natxet/CssMin/src/CssMin.php',
);
public static function getInitializer(ClassLoader $loader)
{
return \Closure::bind(function () use ($loader) {
$loader->prefixLengthsPsr4 = ComposerStaticInitecf10dbf6fe321b2c8e9780228146676::$prefixLengthsPsr4;
$loader->prefixDirsPsr4 = ComposerStaticInitecf10dbf6fe321b2c8e9780228146676::$prefixDirsPsr4;
$loader->prefixesPsr0 = ComposerStaticInitecf10dbf6fe321b2c8e9780228146676::$prefixesPsr0;
$loader->fallbackDirsPsr0 = ComposerStaticInitecf10dbf6fe321b2c8e9780228146676::$fallbackDirsPsr0;
$loader->classMap = ComposerStaticInitecf10dbf6fe321b2c8e9780228146676::$classMap;
}, null, ClassLoader::class);
}
}
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer\Autoload;
/**
* ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
*
* $loader = new \Composer\Autoload\ClassLoader();
*
* // register classes with namespaces
* $loader->add('Symfony\Component', __DIR__.'/component');
* $loader->add('Symfony', __DIR__.'/framework');
*
* // activate the autoloader
* $loader->register();
*
* // to enable searching the include path (eg. for PEAR packages)
* $loader->setUseIncludePath(true);
*
* In this example, if you try to use a class in the Symfony\Component
* namespace or one of its children (Symfony\Component\Console for instance),
* the autoloader will first look for the class under the component/
* directory, and it will then fallback to the framework/ directory if not
* found before giving up.
*
* This class is loosely based on the Symfony UniversalClassLoader.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Jordi Boggiano <j.boggiano@seld.be>
* @see http://www.php-fig.org/psr/psr-0/
* @see http://www.php-fig.org/psr/psr-4/
*/
class ClassLoader
{
// PSR-4
private $prefixLengthsPsr4 = array();
private $prefixDirsPsr4 = array();
private $fallbackDirsPsr4 = array();
// PSR-0
private $prefixesPsr0 = array();
private $fallbackDirsPsr0 = array();
private $useIncludePath = false;
private $classMap = array();
private $classMapAuthoritative = false;
public function getPrefixes()
{
if (!empty($this->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;
}
/**
* @param array $classMap Class to filename map
*/
public function addClassMap(array $classMap)
{
if ($this->classMap) {
$this->classMap = array_merge($this->classMap, $classMap);
} else {
$this->classMap = $classMap;
}
}
/**
* Registers a set of PSR-0 directories for a given prefix, either
* appending or prepending to the ones previously set for this prefix.
*
* @param string $prefix The prefix
* @param array|string $paths The PSR-0 root directories
* @param bool $prepend Whether to prepend the directories
*/
public function add($prefix, $paths, $prepend = false)
{
if (!$prefix) {
if ($prepend) {
$this->fallbackDirsPsr0 = array_merge(
(array) $paths,
$this->fallbackDirsPsr0
);
} else {
$this->fallbackDirsPsr0 = array_merge(
$this->fallbackDirsPsr0,
(array) $paths
);
}
return;
}
$first = $prefix[0];
if (!isset($this->prefixesPsr0[$first][$prefix])) {
$this->prefixesPsr0[$first][$prefix] = (array) $paths;
return;
}
if ($prepend) {
$this->prefixesPsr0[$first][$prefix] = array_merge(
(array) $paths,
$this->prefixesPsr0[$first][$prefix]
);
} else {
$this->prefixesPsr0[$first][$prefix] = array_merge(
$this->prefixesPsr0[$first][$prefix],
(array) $paths
);
}
}
/**
* Registers a set of PSR-4 directories for a given namespace, either
* appending or prepending to the ones previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param array|string $paths The PSR-4 base directories
* @param bool $prepend Whether to prepend the directories
*
* @throws \InvalidArgumentException
*/
public function addPsr4($prefix, $paths, $prepend = false)
{
if (!$prefix) {
// Register directories for the root namespace.
if ($prepend) {
$this->fallbackDirsPsr4 = array_merge(
(array) $paths,
$this->fallbackDirsPsr4
);
} else {
$this->fallbackDirsPsr4 = array_merge(
$this->fallbackDirsPsr4,
(array) $paths
);
}
} elseif (!isset($this->prefixDirsPsr4[$prefix])) {
// Register directories for a new namespace.
$length = strlen($prefix);
if ('\\' !== $prefix[$length - 1]) {
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
$this->prefixDirsPsr4[$prefix] = (array) $paths;
} elseif ($prepend) {
// Prepend directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
(array) $paths,
$this->prefixDirsPsr4[$prefix]
);
} else {
// Append directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
$this->prefixDirsPsr4[$prefix],
(array) $paths
);
}
}
/**
* Registers a set of PSR-0 directories for a given prefix,
* replacing any others previously set for this prefix.
*
* @param string $prefix The prefix
* @param array|string $paths The PSR-0 base directories
*/
public function set($prefix, $paths)
{
if (!$prefix) {
$this->fallbackDirsPsr0 = (array) $paths;
} else {
$this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
}
}
/**
* Registers a set of PSR-4 directories for a given namespace,
* replacing any others previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param array|string $paths The PSR-4 base directories
*
* @throws \InvalidArgumentException
*/
public function setPsr4($prefix, $paths)
{
if (!$prefix) {
$this->fallbackDirsPsr4 = (array) $paths;
} else {
$length = strlen($prefix);
if ('\\' !== $prefix[$length - 1]) {
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
$this->prefixDirsPsr4[$prefix] = (array) $paths;
}
}
/**
* Turns on searching the include path for class files.
*
* @param bool $useIncludePath
*/
public function setUseIncludePath($useIncludePath)
{
$this->useIncludePath = $useIncludePath;
}
/**
* Can be used to check if the autoloader uses the include path to check
* for classes.
*
* @return bool
*/
public function getUseIncludePath()
{
return $this->useIncludePath;
}
/**
* Turns off searching the prefix and fallback directories for classes
* that have not been registered with the class map.
*
* @param bool $classMapAuthoritative
*/
public function setClassMapAuthoritative($classMapAuthoritative)
{
$this->classMapAuthoritative = $classMapAuthoritative;
}
/**
* Should class lookup fail if not found in the current class map?
*
* @return bool
*/
public function isClassMapAuthoritative()
{
return $this->classMapAuthoritative;
}
/**
* Registers this instance as an autoloader.
*
* @param bool $prepend Whether to prepend the autoloader or not
*/
public function register($prepend = false)
{
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
}
/**
* Unregisters this instance as an autoloader.
*/
public function unregister()
{
spl_autoload_unregister(array($this, 'loadClass'));
}
/**
* Loads the given class or interface.
*
* @param string $class The name of the class
* @return bool|null True if loaded, null otherwise
*/
public function loadClass($class)
{
if ($file = $this->findFile($class)) {
includeFile($file);
return true;
}
}
/**
* Finds the path to the file where the class is defined.
*
* @param string $class The name of the class
*
* @return string|false The path if found, false otherwise
*/
public function findFile($class)
{
// work around for PHP 5.3.0 - 5.3.2 https://bugs.php.net/50731
if ('\\' == $class[0]) {
$class = substr($class, 1);
}
// class map lookup
if (isset($this->classMap[$class])) {
return $this->classMap[$class];
}
if ($this->classMapAuthoritative) {
return false;
}
$file = $this->findFileWithExtension($class, '.php');
// Search for Hack files if we are running on HHVM
if ($file === null && defined('HHVM_VERSION')) {
$file = $this->findFileWithExtension($class, '.hh');
}
if ($file === null) {
// Remember that this class does not exist.
return $this->classMap[$class] = false;
}
return $file;
}
private function findFileWithExtension($class, $ext)
{
// PSR-4 lookup
$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
$first = $class[0];
if (isset($this->prefixLengthsPsr4[$first])) {
foreach ($this->prefixLengthsPsr4[$first] as $prefix => $length) {
if (0 === strpos($class, $prefix)) {
foreach ($this->prefixDirsPsr4[$prefix] as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) {
return $file;
}
}
}
}
}
// PSR-4 fallback dirs
foreach ($this->fallbackDirsPsr4 as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
return $file;
}
}
// PSR-0 lookup
if (false !== $pos = strrpos($class, '\\')) {
// namespaced class name
$logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
. strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
} else {
// PEAR-like class name
$logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
}
if (isset($this->prefixesPsr0[$first])) {
foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
if (0 === strpos($class, $prefix)) {
foreach ($dirs as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}
}
}
}
// PSR-0 fallback dirs
foreach ($this->fallbackDirsPsr0 as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}
// PSR-0 include paths.
if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
return $file;
}
}
}
/**
* Scope isolated include.
*
* Prevents access to $this/self from included files.
*/
function includeFile($file)
{
include $file;
}
<?php
// include_paths.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
$vendorDir . '/pear/pear_exception',
$vendorDir . '/pear/console_getopt',
$vendorDir . '/pear/pear-core-minimal/src',
$vendorDir . '/pear/archive_tar',
);
<?php
namespace Consolidation\AnnotatedCommand;
use Consolidation\AnnotatedCommand\Hooks\HookManager;
use Consolidation\AnnotatedCommand\Parser\CommandInfo;
use Consolidation\OutputFormatters\FormatterManager;
use Consolidation\OutputFormatters\Options\FormatterOptions;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
* AnnotatedCommands are created automatically by the
* AnnotatedCommandFactory. Each command method in a
* command file will produce one AnnotatedCommand. These
* are then added to your Symfony Console Application object;
* nothing else is needed.
*
* Optionally, though, you may extend AnnotatedCommand directly
* to make a single command. The usage pattern is the same
* as for any other Symfony Console command, except that you may
* omit the 'Confiure' method, and instead place your annotations
* on the execute() method.
*
* @package Consolidation\AnnotatedCommand
*/
class AnnotatedCommand extends Command
{
protected $commandCallback;
protected $commandProcessor;
protected $annotationData;
protected $usesInputInterface;
protected $usesOutputInterface;
protected $returnType;
public function __construct($name = null)
{
$commandInfo = false;
// If this is a subclass of AnnotatedCommand, check to see
// if the 'execute' method is annotated. We could do this
// unconditionally; it is a performance optimization to skip
// checking the annotations if $this is an instance of
// AnnotatedCommand. Alternately, we break out a new subclass.
// The command factory instantiates the subclass.
if (get_class($this) != 'Consolidation\AnnotatedCommand\AnnotatedCommand') {
$commandInfo = new CommandInfo($this, 'execute');
if (!isset($name)) {
$name = $commandInfo->getName();
}
}
parent::__construct($name);
if ($commandInfo && $commandInfo->hasAnnotation('command')) {
$this->setCommandInfo($commandInfo);
$this->setCommandOptions($commandInfo);
}
}
public function setCommandCallback($commandCallback)
{
$this->commandCallback = $commandCallback;
return $this;
}
public function setCommandProcessor($commandProcessor)
{
$this->commandProcessor = $commandProcessor;
return $this;
}
public function commandProcessor()
{
// If someone is using an AnnotatedCommand, and is NOT getting
// it from an AnnotatedCommandFactory OR not correctly injecting
// a command processor via setCommandProcessor() (ideally via the
// DI container), then we'll just give each annotated command its
// own command processor. This is not ideal; preferably, there would
// only be one instance of the command processor in the application.
if (!isset($this->commandProcessor)) {
$this->commandProcessor = new CommandProcessor(new HookManager());
}
return $this->commandProcessor;
}
public function getReturnType()
{
return $this->returnType;
}
public function setReturnType($returnType)
{
$this->returnType = $returnType;
return $this;
}
public function getAnnotationData()
{
return $this->annotationData;
}
public function setAnnotationData($annotationData)
{
$this->annotationData = $annotationData;
return $this;
}
public function setCommandInfo($commandInfo)
{
$this->setDescription($commandInfo->getDescription());
$this->setHelp($commandInfo->getHelp());
$this->setAliases($commandInfo->getAliases());
$this->setAnnotationData($commandInfo->getAnnotations());
foreach ($commandInfo->getExampleUsages() as $usage => $description) {
// Symfony Console does not support attaching a description to a usage
$this->addUsage($usage);
}
$this->setCommandArguments($commandInfo);
$this->setReturnType($commandInfo->getReturnType());
return $this;
}
protected function setCommandArguments($commandInfo)
{
$this->setUsesInputInterface($commandInfo);
$this->setUsesOutputInterface($commandInfo);
$this->setCommandArgumentsFromParameters($commandInfo);
return $this;
}
/**
* Check whether the first parameter is an InputInterface.
*/
protected function checkUsesInputInterface($params)
{
$firstParam = reset($params);
return $firstParam instanceof InputInterface;
}
/**
* Determine whether this command wants to get its inputs
* via an InputInterface or via its command parameters
*/
protected function setUsesInputInterface($commandInfo)
{
$params = $commandInfo->getParameters();
$this->usesInputInterface = $this->checkUsesInputInterface($params);
return $this;
}
/**
* Determine whether this command wants to send its output directly
* to the provided OutputInterface, or whether it will returned
* structured output to be processed by the command processor.
*/
protected function setUsesOutputInterface($commandInfo)
{
$params = $commandInfo->getParameters();
$index = $this->checkUsesInputInterface($params) ? 1 : 0;
$this->usesOutputInterface =
(count($params) > $index) &&
($params[$index] instanceof OutputInterface);
return $this;
}
protected function setCommandArgumentsFromParameters($commandInfo)
{
$args = $commandInfo->arguments()->getValues();
foreach ($args as $name => $defaultValue) {
$description = $commandInfo->arguments()->getDescription($name);
$hasDefault = $commandInfo->arguments()->hasDefault($name);
$parameterMode = $this->getCommandArgumentMode($hasDefault, $defaultValue);
$this->addArgument($name, $parameterMode, $description, $defaultValue);
}
return $this;
}
protected function getCommandArgumentMode($hasDefault, $defaultValue)
{
if (!$hasDefault) {
return InputArgument::REQUIRED;
}
if (is_array($defaultValue)) {
return InputArgument::IS_ARRAY;
}
return InputArgument::OPTIONAL;
}
public function setCommandOptions($commandInfo, $automaticOptions = [])
{
$inputOptions = $commandInfo->inputOptions();
$this->addOptions($inputOptions + $automaticOptions, $automaticOptions);
return $this;
}
public function addOptions($inputOptions, $automaticOptions = [])
{
foreach ($inputOptions as $name => $inputOption) {
$description = $inputOption->getDescription();
if (empty($description) && isset($automaticOptions[$name])) {
$description = $automaticOptions[$name]->getDescription();
$inputOption = static::inputOptionSetDescription($inputOption, $description);
}
$this->getDefinition()->addOption($inputOption);
}
}
protected static function inputOptionSetDescription($inputOption, $description)
{
// Recover the 'mode' value, because Symfony is stubborn
$mode = 0;
if ($inputOption->isValueRequired()) {
$mode |= InputOption::VALUE_REQUIRED;
}
if ($inputOption->isValueOptional()) {
$mode |= InputOption::VALUE_OPTIONAL;
}
if ($inputOption->isArray()) {
$mode |= InputOption::VALUE_IS_ARRAY;
}
if (!$mode) {
$mode = InputOption::VALUE_NONE;
}
$inputOption = new InputOption(
$inputOption->getName(),
$inputOption->getShortcut(),
$mode,
$description,
$inputOption->getDefault()
);
return $inputOption;
}
/**
* Returns all of the hook names that may be called for this command.
*
* @return array
*/
public function getNames()
{
return HookManager::getNames($this, $this->commandCallback);
}
/**
* Add any options to this command that are defined by hook implementations
*/
public function optionsHook()
{
$this->commandProcessor()->optionsHook(
$this,
$this->getNames(),
$this->annotationData
);
}
public function optionsHookForHookAnnotations($commandInfoList)
{
foreach ($commandInfoList as $commandInfo) {
$inputOptions = $commandInfo->inputOptions();
$this->addOptions($inputOptions);
foreach ($commandInfo->getExampleUsages() as $usage => $description) {
if (!in_array($usage, $this->getUsages())) {
$this->addUsage($usage);
}
}
}
}
/**
* {@inheritdoc}
*/
protected function interact(InputInterface $input, OutputInterface $output)
{
$this->commandProcessor()->interact(
$input,
$output,
$this->getNames(),
$this->annotationData
);
}
protected function initialize(InputInterface $input, OutputInterface $output)
{
// Allow the hook manager a chance to provide configuration values,
// if there are any registered hooks to do that.
$this->commandProcessor()->initializeHook($input, $this->getNames(), $this->annotationData);
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
// Validate, run, process, alter, handle results.
return $this->commandProcessor()->process(
$output,
$this->getNames(),
$this->commandCallback,
$this->createCommandData($input, $output)
);
}
/**
* This function is available for use by a class that may
* wish to extend this class rather than use annotations to
* define commands. Using this technique does allow for the
* use of annotations to define hooks.
*/
public function processResults(InputInterface $input, OutputInterface $output, $results)
{
$commandData = $this->createCommandData($input, $output);
$commandProcessor = $this->commandProcessor();
$names = $this->getNames();
$results = $commandProcessor->processResults(
$names,
$results,
$commandData
);
return $commandProcessor->handleResults(
$output,
$names,
$results,
$commandData
);
}
protected function createCommandData(InputInterface $input, OutputInterface $output)
{
$commandData = new CommandData(
$this->annotationData,
$input,
$output
);
$commandData->setUseIOInterfaces(
$this->usesOutputInterface,
$this->usesInputInterface
);
return $commandData;
}
}
<?php
namespace Consolidation\AnnotatedCommand;
use Consolidation\AnnotatedCommand\Hooks\HookManager;
use Consolidation\AnnotatedCommand\Options\AutomaticOptionsProviderInterface;
use Consolidation\AnnotatedCommand\Parser\CommandInfo;
use Consolidation\OutputFormatters\Options\FormatterOptions;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* The AnnotatedCommandFactory creates commands for your application.
* Use with a Dependency Injection Container and the CommandFactory.
* Alternately, use the CommandFileDiscovery to find commandfiles, and
* then use AnnotatedCommandFactory::createCommandsFromClass() to create
* commands. See the README for more information.
*
* @package Consolidation\AnnotatedCommand
*/
class AnnotatedCommandFactory implements AutomaticOptionsProviderInterface
{
/** var CommandProcessor */
protected $commandProcessor;
/** var CommandCreationListenerInterface[] */
protected $listeners = [];
/** var AutomaticOptionsProvider[] */
protected $automaticOptionsProviderList = [];
/** var boolean */
protected $includeAllPublicMethods = true;
/** var CommandInfoAltererInterface */
protected $commandInfoAlterers = [];
public function __construct()
{
$this->commandProcessor = new CommandProcessor(new HookManager());
$this->addAutomaticOptionProvider($this);
}
public function setCommandProcessor(CommandProcessor $commandProcessor)
{
$this->commandProcessor = $commandProcessor;
return $this;
}
/**
* @return CommandProcessor
*/
public function commandProcessor()
{
return $this->commandProcessor;
}
/**
* Set the 'include all public methods flag'. If true (the default), then
* every public method of each commandFile will be used to create commands.
* If it is false, then only those public methods annotated with @command
* or @name (deprecated) will be used to create commands.
*/
public function setIncludeAllPublicMethods($includeAllPublicMethods)
{
$this->includeAllPublicMethods = $includeAllPublicMethods;
return $this;
}
public function getIncludeAllPublicMethods()
{
return $this->includeAllPublicMethods;
}
/**
* @return HookManager
*/
public function hookManager()
{
return $this->commandProcessor()->hookManager();
}
/**
* Add a listener that is notified immediately before the command
* factory creates commands from a commandFile instance. This
* listener can use this opportunity to do more setup for the commandFile,
* and so on.
*
* @param CommandCreationListenerInterface $listener
*/
public function addListener(CommandCreationListenerInterface $listener)
{
$this->listeners[] = $listener;
}
/**
* Call all command creation listeners
*
* @param object $commandFileInstance
*/
protected function notify($commandFileInstance)
{
foreach ($this->listeners as $listener) {
$listener->notifyCommandFileAdded($commandFileInstance);
}
}
public function addAutomaticOptionProvider(AutomaticOptionsProviderInterface $optionsProvider)
{
$this->automaticOptionsProviderList[] = $optionsProvider;
}
public function addCommandInfoAlterer(CommandInfoAltererInterface $alterer)
{
$this->commandInfoAlterers[] = $alterer;
}
/**
* n.b. This registers all hooks from the commandfile instance as a side-effect.
*/
public function createCommandsFromClass($commandFileInstance, $includeAllPublicMethods = null)
{
// Deprecated: avoid using the $includeAllPublicMethods in favor of the setIncludeAllPublicMethods() accessor.
if (!isset($includeAllPublicMethods)) {
$includeAllPublicMethods = $this->getIncludeAllPublicMethods();
}
$this->notify($commandFileInstance);
$commandInfoList = $this->getCommandInfoListFromClass($commandFileInstance);
$this->registerCommandHooksFromClassInfo($commandInfoList, $commandFileInstance);
return $this->createCommandsFromClassInfo($commandInfoList, $commandFileInstance, $includeAllPublicMethods);
}
public function getCommandInfoListFromClass($classNameOrInstance)
{
$commandInfoList = [];
// Ignore special functions, such as __construct and __call, which
// can never be commands.
$commandMethodNames = array_filter(
get_class_methods($classNameOrInstance) ?: [],
function ($m) {
return !preg_match('#^_#', $m);
}
);
foreach ($commandMethodNames as $commandMethodName) {
$commandInfoList[] = new CommandInfo($classNameOrInstance, $commandMethodName);
}
return $commandInfoList;
}
public function createCommandInfo($classNameOrInstance, $commandMethodName)
{
return new CommandInfo($classNameOrInstance, $commandMethodName);
}
public function createCommandsFromClassInfo($commandInfoList, $commandFileInstance, $includeAllPublicMethods = null)
{
// Deprecated: avoid using the $includeAllPublicMethods in favor of the setIncludeAllPublicMethods() accessor.
if (!isset($includeAllPublicMethods)) {
$includeAllPublicMethods = $this->getIncludeAllPublicMethods();
}
return $this->createSelectedCommandsFromClassInfo(
$commandInfoList,
$commandFileInstance,
function ($commandInfo) use ($includeAllPublicMethods) {
return static::isCommandMethod($commandInfo, $includeAllPublicMethods);
}
);
}
public function createSelectedCommandsFromClassInfo($commandInfoList, $commandFileInstance, callable $commandSelector)
{
$commandList = [];
foreach ($commandInfoList as $commandInfo) {
if ($commandSelector($commandInfo)) {
$command = $this->createCommand($commandInfo, $commandFileInstance);
$commandList[] = $command;
}
}
return $commandList;
}
public static function isCommandMethod($commandInfo, $includeAllPublicMethods)
{
// Ignore everything labeled @hook
if ($commandInfo->hasAnnotation('hook')) {
return false;
}
// Include everything labeled @command
if ($commandInfo->hasAnnotation('command')) {
return true;
}
// Skip anything named like an accessor ('get' or 'set')
if (preg_match('#^(get[A-Z]|set[A-Z])#', $commandInfo->getMethodName())) {
return false;
}
// Default to the setting of 'include all public methods'.
return $includeAllPublicMethods;
}
public function registerCommandHooksFromClassInfo($commandInfoList, $commandFileInstance)
{
foreach ($commandInfoList as $commandInfo) {
if ($commandInfo->hasAnnotation('hook')) {
$this->registerCommandHook($commandInfo, $commandFileInstance);
}
}
}
/**
* Register a command hook given the CommandInfo for a method.
*
* The hook format is:
*
* @hook type name type
*
* For example, the pre-validate hook for the core:init command is:
*
* @hook pre-validate core:init
*
* If no command name is provided, then this hook will affect every
* command that is defined in the same file.
*
* If no hook is provided, then we will presume that ALTER_RESULT
* is intended.
*
* @param CommandInfo $commandInfo Information about the command hook method.
* @param object $commandFileInstance An instance of the CommandFile class.
*/
public function registerCommandHook(CommandInfo $commandInfo, $commandFileInstance)
{
// Ignore if the command info has no @hook
if (!$commandInfo->hasAnnotation('hook')) {
return;
}
$hookData = $commandInfo->getAnnotation('hook');
$hook = $this->getNthWord($hookData, 0, HookManager::ALTER_RESULT);
$commandName = $this->getNthWord($hookData, 1);
// Register the hook
$callback = [$commandFileInstance, $commandInfo->getMethodName()];
$this->commandProcessor()->hookManager()->add($callback, $hook, $commandName);
// If the hook has options, then also register the commandInfo
// with the hook manager, so that we can add options and such to
// the commands they hook.
if (!$commandInfo->options()->isEmpty()) {
$this->commandProcessor()->hookManager()->recordHookOptions($commandInfo, $commandName);
}
}
protected function getNthWord($string, $n, $default = '', $delimiter = ' ')
{
$words = explode($delimiter, $string);
if (!empty($words[$n])) {
return $words[$n];
}
return $default;
}
public function createCommand(CommandInfo $commandInfo, $commandFileInstance)
{
$this->alterCommandInfo($commandInfo, $commandFileInstance);
$command = new AnnotatedCommand($commandInfo->getName());
$commandCallback = [$commandFileInstance, $commandInfo->getMethodName()];
$command->setCommandCallback($commandCallback);
$command->setCommandProcessor($this->commandProcessor);
$command->setCommandInfo($commandInfo);
$automaticOptions = $this->callAutomaticOptionsProviders($commandInfo);
$command->setCommandOptions($commandInfo, $automaticOptions);
// Annotation commands are never bootstrap-aware, but for completeness
// we will notify on every created command, as some clients may wish to
// use this notification for some other purpose.
$this->notify($command);
return $command;
}
/**
* Give plugins an opportunity to update the commandInfo
*/
public function alterCommandInfo(CommandInfo $commandInfo, $commandFileInstance)
{
foreach ($this->commandInfoAlterers as $alterer) {
$alterer->alterCommandInfo($commandInfo, $commandFileInstance);
}
}
/**
* Get the options that are implied by annotations, e.g. @fields implies
* that there should be a --fields and a --format option.
*
* @return InputOption[]
*/
public function callAutomaticOptionsProviders(CommandInfo $commandInfo)
{
$automaticOptions = [];
foreach ($this->automaticOptionsProviderList as $automaticOptionsProvider) {
$automaticOptions += $automaticOptionsProvider->automaticOptions($commandInfo);
}
return $automaticOptions;
}
/**
* Get the options that are implied by annotations, e.g. @fields implies
* that there should be a --fields and a --format option.
*
* @return InputOption[]
*/
public function automaticOptions(CommandInfo $commandInfo)
{
$automaticOptions = [];
$formatManager = $this->commandProcessor()->formatterManager();
if ($formatManager) {
$annotationData = $commandInfo->getAnnotations()->getArrayCopy();
$formatterOptions = new FormatterOptions($annotationData);
$dataType = $commandInfo->getReturnType();
$automaticOptions = $formatManager->automaticOptions($formatterOptions, $dataType);
}
return $automaticOptions;
}
}
<?php
namespace Consolidation\AnnotatedCommand;
class AnnotationData extends \ArrayObject
{
public function get($key, $default)
{
return $this->has($key) ? $this[$key] : $default;
}
public function has($key)
{
return isset($this[$key]);
}
public function keys()
{
return array_keys($this->getArrayCopy());
}
}
<?php
namespace Consolidation\AnnotatedCommand;
/**
* Command cration listeners can be added to the annotation
* command factory. These will be notified whenever a new
* commandfile is provided to the factory. This is useful for
* initializing new commandfile objects.
*
* @see AnnotatedCommandFactory::addListener()
*/
interface CommandCreationListenerInterface
{
public function notifyCommandFileAdded($command);
}
<?php
namespace Consolidation\AnnotatedCommand;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class CommandData
{
/** var AnnotationData */
protected $annotationData;
/** var InputInterface */
protected $input;
/** var OutputInterface */
protected $output;
/** var boolean */
protected $usesInputInterface;
/** var boolean */
protected $usesOutputInterface;
/** var boolean */
protected $includeOptionsInArgs;
public function __construct(
AnnotationData $annotationData,
InputInterface $input,
OutputInterface $output,
$usesInputInterface = false,
$usesOutputInterface = false
) {
$this->annotationData = $annotationData;
$this->input = $input;
$this->output = $output;
$this->usesInputInterface = false;
$this->usesOutputInterface = false;
$this->includeOptionsInArgs = true;
}
/**
* For internal use only; indicates that the function to be called
* should be passed an InputInterface &/or an OutputInterface.
* @param booean $usesInputInterface
* @param boolean $usesOutputInterface
* @return self
*/
public function setUseIOInterfaces($usesInputInterface, $usesOutputInterface)
{
$this->usesInputInterface = $usesInputInterface;
$this->usesOutputInterface = $usesOutputInterface;
return $this;
}
/**
* For backwards-compatibility mode only: disable addition of
* options on the end of the arguments list.
*/
public function setIncludeOptionsInArgs($includeOptionsInArgs)
{
$this->includeOptionsInArgs = $includeOptionsInArgs;
return $this;
}
public function annotationData()
{
return $this->annotationData;
}
public function input()
{
return $this->input;
}
public function output()
{
return $this->output;
}
public function arguments()
{
return $this->input->getArguments();
}
public function options()
{
return $this->input->getOptions();
}
public function getArgsWithoutAppName()
{
$args = $this->arguments();
// When called via the Application, the first argument
// will be the command name. The Application alters the
// input definition to match, adding a 'command' argument
// to the beginning.
array_shift($args);
if ($this->usesInputInterface) {
array_unshift($args, $this->input());
}
if ($this->usesOutputInterface) {
array_unshift($args, $this->output());
}
return $args;
}
public function getArgsAndOptions()
{
// Get passthrough args, and add the options on the end.
$args = $this->getArgsWithoutAppName();
if ($this->includeOptionsInArgs) {
$args['options'] = $this->options();
}
return $args;
}
}
<?php
namespace Consolidation\AnnotatedCommand;
/**
* Return a CommandError as the result of a command to pass a status
* code and error message to be displayed.
*
* @package Consolidation\AnnotatedCommand
*/
class CommandError implements ExitCodeInterface, OutputDataInterface
{
protected $message;
protected $exitCode;
public function __construct($message = null, $exitCode = 1)
{
$this->message = $message;
// Ensure the exit code is non-zero. The exit code may have
// come from an exception, and those often default to zero if
// a specific value is not provided.
$this->exitCode = $exitCode == 0 ? 1 : $exitCode;
}
public function getExitCode()
{
return $this->exitCode;
}
public function getOutputData()
{
return $this->message;
}
}
<?php
namespace Consolidation\AnnotatedCommand;
use Symfony\Component\Finder\Finder;
/**
* Do discovery presuming that the namespace of the command will
* contain the last component of the path. This is the convention
* that should be used when searching for command files that are
* bundled with the modules of a framework. The convention used
* is that the namespace for a module in a framework should start with
* the framework name followed by the module name.
*
* For example, if base namespace is "Drupal", then a command file in
* modules/default_content/src/CliTools/ExampleCommands.cpp
* will be in the namespace Drupal\default_content\CliTools.
*
* For global locations, the middle component of the namespace is
* omitted. For example, if the base namespace is "Drupal", then
* a command file in __DRUPAL_ROOT__/CliTools/ExampleCommands.cpp
* will be in the namespace Drupal\CliTools.
*
* To discover namespaced commands in modules:
*
* $commandFiles = $discovery->discoverNamespaced($moduleList, '\Drupal');
*
* To discover global commands:
*
* $commandFiles = $discovery->discover($drupalRoot, '\Drupal');
*/
class CommandFileDiscovery
{
/** @var string[] */
protected $excludeList;
/** @var string[] */
protected $searchLocations;
/** @var string */
protected $searchPattern = '*Commands.php';
/** @var boolean */
protected $includeFilesAtBase = true;
/** @var integer */
protected $searchDepth = 2;
public function __construct()
{
$this->excludeList = ['Exclude'];
$this->searchLocations = [
'Command',
'CliTools', // TODO: Maybe remove
];
}
/**
* Specify whether to search for files at the base directory
* ($directoryList parameter to discover and discoverNamespaced
* methods), or only in the directories listed in the search paths.
*
* @param boolean $includeFilesAtBase
*/
public function setIncludeFilesAtBase($includeFilesAtBase)
{
$this->includeFilesAtBase = $includeFilesAtBase;
return $this;
}
/**
* Set the list of excludes to add to the finder, replacing
* whatever was there before.
*
* @param array $excludeList The list of directory names to skip when
* searching for command files.
*/
public function setExcludeList($excludeList)
{
$this->excludeList = $excludeList;
return $this;
}
/**
* Add one more location to the exclude list.
*
* @param string $exclude One directory name to skip when searching
* for command files.
*/
public function addExclude($exclude)
{
$this->excludeList[] = $exclude;
return $this;
}
/**
* Set the search depth. By default, fills immediately in the
* base directory are searched, plus all of the search locations
* to this specified depth. If the search locations is set to
* an empty array, then the base directory is searched to this
* depth.
*/
public function setSearchDepth($searchDepth)
{
$this->searchDepth = $searchDepth;
return $this;
}
/**
* Set the list of search locations to examine in each directory where
* command files may be found. This replaces whatever was there before.
*
* @param array $searchLocations The list of locations to search for command files.
*/
public function setSearchLocations($searchLocations)
{
$this->searchLocations = $searchLocations;
return $this;
}
/**
* Add one more location to the search location list.
*
* @param string $location One more relative path to search
* for command files.
*/
public function addSearchLocation($location)
{
$this->searchLocations[] = $location;
return $this;
}
/**
* Specify the pattern / regex used by the finder to search for
* command files.
*/
public function setSearchPattern($searchPattern)
{
$this->searchPattern = $searchPattern;
return $this;
}
/**
* Given a list of directories, e.g. Drupal modules like:
*
* core/modules/block
* core/modules/dblog
* modules/default_content
*
* Discover command files in any of these locations.
*
* @param string|string[] $directoryList Places to search for commands.
*
* @return array
*/
public function discoverNamespaced($directoryList, $baseNamespace = '')
{
return $this->discover($this->convertToNamespacedList((array)$directoryList), $baseNamespace);
}
/**
* Given a simple list containing paths to directories, where
* the last component of the path should appear in the namespace,
* after the base namespace, this function will return an
* associative array mapping the path's basename (e.g. the module
* name) to the directory path.
*
* Module names must be unique.
*
* @param string[] $directoryList A list of module locations
*
* @return array
*/
public function convertToNamespacedList($directoryList)
{
$namespacedArray = [];
foreach ((array)$directoryList as $directory) {
$namespacedArray[basename($directory)] = $directory;
}
return $namespacedArray;
}
/**
* Search for command files in the specified locations. This is the function that
* should be used for all locations that are NOT modules of a framework.
*
* @param string|string[] $directoryList Places to search for commands.
* @return array
*/
public function discover($directoryList, $baseNamespace = '')
{
$commandFiles = [];
foreach ((array)$directoryList as $key => $directory) {
$itemsNamespace = $this->joinNamespace([$baseNamespace, $key]);
$commandFiles = array_merge(
$commandFiles,
$this->discoverCommandFiles($directory, $itemsNamespace),
$this->discoverCommandFiles("$directory/src", $itemsNamespace)
);
}
return $commandFiles;
}
/**
* Search for command files in specific locations within a single directory.
*
* In each location, we will accept only a few places where command files
* can be found. This will reduce the need to search through many unrelated
* files.
*
* The default search locations include:
*
* .
* CliTools
* src/CliTools
*
* The pattern we will look for is any file whose name ends in 'Commands.php'.
* A list of paths to found files will be returned.
*/
protected function discoverCommandFiles($directory, $baseNamespace)
{
$commandFiles = [];
// In the search location itself, we will search for command files
// immediately inside the directory only.
if ($this->includeFilesAtBase) {
$commandFiles = $this->discoverCommandFilesInLocation(
$directory,
$this->getBaseDirectorySearchDepth(),
$baseNamespace
);
}
// In the other search locations,
foreach ($this->searchLocations as $location) {
$itemsNamespace = $this->joinNamespace([$baseNamespace, $location]);
$commandFiles = array_merge(
$commandFiles,
$this->discoverCommandFilesInLocation(
"$directory/$location",
$this->getSearchDepth(),
$itemsNamespace
)
);
}
return $commandFiles;
}
/**
* Return a Finder search depth appropriate for our selected search depth.
*
* @return string
*/
protected function getSearchDepth()
{
return $this->searchDepth <= 0 ? '== 0' : '<= ' . $this->searchDepth;
}
/**
* Return a Finder search depth for the base directory. If the
* searchLocations array has been populated, then we will only search
* for files immediately inside the base directory; no traversal into
* deeper directories will be done, as that would conflict with the
* specification provided by the search locations. If there is no
* search location, then we will search to whatever depth was specified
* by the client.
*
* @return string
*/
protected function getBaseDirectorySearchDepth()
{
if (!empty($this->searchLocations)) {
return '== 0';
}
return $this->getSearchDepth();
}
/**
* Search for command files in just one particular location. Returns
* an associative array mapping from the pathname of the file to the
* classname that it contains. The pathname may be ignored if the search
* location is included in the autoloader.
*
* @param string $directory The location to search
* @param string $depth How deep to search (e.g. '== 0' or '< 2')
* @param string $baseNamespace Namespace to prepend to each classname
*
* @return array
*/
protected function discoverCommandFilesInLocation($directory, $depth, $baseNamespace)
{
if (!is_dir($directory)) {
return [];
}
$finder = $this->createFinder($directory, $depth);
$commands = [];
foreach ($finder as $file) {
$relativePathName = $file->getRelativePathname();
$relativeNamespaceAndClassname = str_replace(
['/', '.php'],
['\\', ''],
$relativePathName
);
$classname = $this->joinNamespace([$baseNamespace, $relativeNamespaceAndClassname]);
$commandFilePath = $this->joinPaths([$directory, $relativePathName]);
$commands[$commandFilePath] = $classname;
}
return $commands;
}
/**
* Create a Finder object for use in searching a particular directory
* location.
*
* @param string $directory The location to search
* @param string $depth The depth limitation
*
* @return Finder
*/
protected function createFinder($directory, $depth)
{
$finder = new Finder();
$finder->files()
->name($this->searchPattern)
->in($directory)
->depth($depth);
foreach ($this->excludeList as $item) {
$finder->exclude($item);
}
return $finder;
}
/**
* Combine the items of the provied array into a backslash-separated
* namespace string. Empty and numeric items are omitted.
*
* @param array $namespaceParts List of components of a namespace
*
* @return string
*/
protected function joinNamespace(array $namespaceParts)
{
return $this->joinParts(
'\\',
$namespaceParts,
function ($item) {
return !is_numeric($item) && !empty($item);
}
);
}
/**
* Combine the items of the provied array into a slash-separated
* pathname. Empty items are omitted.
*
* @param array $pathParts List of components of a path
*
* @return string
*/
protected function joinPaths(array $pathParts)
{
return $this->joinParts(
'/',
$pathParts,
function ($item) {
return !empty($item);
}
);
}
/**
* Simple wrapper around implode and array_filter.
*
* @param string $delimiter
* @param array $parts
* @param callable $filterFunction
*/
protected function joinParts($delimiter, $parts, $filterFunction)
{
return implode(
$delimiter,
array_filter($parts, $filterFunction)
);
}
}
<?php
namespace Consolidation\AnnotatedCommand;
use Consolidation\AnnotatedCommand\Parser\CommandInfo;
interface CommandInfoAltererInterface
{
public function alterCommandInfo(CommandInfo $commandInfo, $commandFileInstance);
}
<?php
namespace Consolidation\AnnotatedCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Consolidation\OutputFormatters\FormatterManager;
use Consolidation\OutputFormatters\Options\FormatterOptions;
use Consolidation\AnnotatedCommand\Hooks\HookManager;
/**
* Process a command, including hooks and other callbacks.
* There should only be one command processor per application.
* Provide your command processor to the AnnotatedCommandFactory
* via AnnotatedCommandFactory::setCommandProcessor().
*/
class CommandProcessor
{
/** var HookManager */
protected $hookManager;
/** var FormatterManager */
protected $formatterManager;
/** var callable */
protected $displayErrorFunction;
public function __construct(HookManager $hookManager)
{
$this->hookManager = $hookManager;
}
/**
* Return the hook manager
* @return HookManager
*/
public function hookManager()
{
return $this->hookManager;
}
public function setFormatterManager(FormatterManager $formatterManager)
{
$this->formatterManager = $formatterManager;
return $this;
}
public function setDisplayErrorFunction(callable $fn)
{
$this->displayErrorFunction = $fn;
return $this;
}
/**
* Return the formatter manager
* @return FormatterManager
*/
public function formatterManager()
{
return $this->formatterManager;
}
public function initializeHook(
InputInterface $input,
$names,
AnnotationData $annotationData
) {
return $this->hookManager()->initializeHook($input, $names, $annotationData);
}
public function optionsHook(
AnnotatedCommand $command,
$names,
AnnotationData $annotationData
) {
$this->hookManager()->optionsHook($command, $names, $annotationData);
}
public function interact(
InputInterface $input,
OutputInterface $output,
$names,
AnnotationData $annotationData
) {
return $this->hookManager()->interact($input, $output, $names, $annotationData);
}
public function process(
OutputInterface $output,
$names,
$commandCallback,
CommandData $commandData
) {
$result = [];
try {
$result = $this->validateRunAndAlter(
$names,
$commandCallback,
$commandData
);
return $this->handleResults($output, $names, $result, $commandData);
} catch (\Exception $e) {
$result = new CommandError($e->getMessage(), $e->getCode());
return $this->handleResults($output, $names, $result, $commandData);
}
}
public function validateRunAndAlter(
$names,
$commandCallback,
CommandData $commandData
) {
// Validators return any object to signal a validation error;
// if the return an array, it replaces the arguments.
$validated = $this->hookManager()->validateArguments($names, $commandData);
if (is_object($validated)) {
return $validated;
}
// Run the command, alter the results, and then handle output and status
$result = $this->runCommandCallback($commandCallback, $commandData);
return $this->processResults($names, $result, $commandData);
}
public function processResults($names, $result, CommandData $commandData)
{
return $this->hookManager()->alterResult($names, $result, $commandData);
}
/**
* Handle the result output and status code calculation.
*/
public function handleResults(OutputInterface $output, $names, $result, CommandData $commandData)
{
$status = $this->hookManager()->determineStatusCode($names, $result);
// If the result is an integer and no separate status code was provided, then use the result as the status and do no output.
if (is_integer($result) && !isset($status)) {
return $result;
}
$status = $this->interpretStatusCode($status);
// Get the structured output, the output stream and the formatter
$structuredOutput = $this->hookManager()->extractOutput($names, $result);
$output = $this->chooseOutputStream($output, $status);
if ($status != 0) {
return $this->writeErrorMessage($output, $status, $structuredOutput, $result);
}
if ($this->dataCanBeFormatted($structuredOutput) && isset($this->formatterManager)) {
return $this->writeUsingFormatter($output, $structuredOutput, $commandData);
}
return $this->writeCommandOutput($output, $structuredOutput);
}
protected function dataCanBeFormatted($structuredOutput)
{
if (!isset($this->formatterManager)) {
return false;
}
return
is_object($structuredOutput) ||
is_array($structuredOutput);
}
/**
* Run the main command callback
*/
protected function runCommandCallback($commandCallback, CommandData $commandData)
{
$result = false;
try {
$args = $commandData->getArgsAndOptions();
$result = call_user_func_array($commandCallback, $args);
} catch (\Exception $e) {
$result = new CommandError($e->getMessage(), $e->getCode());
}
return $result;
}
/**
* Determine the formatter that should be used to render
* output.
*
* If the user specified a format via the --format option,
* then always return that. Otherwise, return the default
* format, unless --pipe was specified, in which case
* return the default pipe format, format-pipe.
*
* n.b. --pipe is a handy option introduced in Drush 2
* (or perhaps even Drush 1) that indicates that the command
* should select the output format that is most appropriate
* for use in scripts (e.g. to pipe to another command).
*
* @return string
*/
protected function getFormat($options)
{
// In Symfony Console, there is no way for us to differentiate
// between the user specifying '--format=table', and the user
// not specifying --format when the default value is 'table'.
// Therefore, we must make --field always override --format; it
// cannot become the default value for --format.
if (!empty($options['field'])) {
return 'string';
}
$options += [
'default-format' => false,
'pipe' => false,
];
$options += [
'format' => $options['default-format'],
'format-pipe' => $options['default-format'],
];
$format = $options['format'];
if ($options['pipe']) {
$format = $options['format-pipe'];
}
return $format;
}
/**
* Determine whether we should use stdout or stderr.
*/
protected function chooseOutputStream(OutputInterface $output, $status)
{
// If the status code indicates an error, then print the
// result to stderr rather than stdout
if ($status && ($output instanceof ConsoleOutputInterface)) {
return $output->getErrorOutput();
}
return $output;
}
/**
* Call the formatter to output the provided data.
*/
protected function writeUsingFormatter(OutputInterface $output, $structuredOutput, CommandData $commandData)
{
$options = $commandData->input()->getOptions();
$format = $this->getFormat($options);
$formatterOptions = new FormatterOptions($commandData->annotationData()->getArrayCopy(), $options);
$this->formatterManager->write(
$output,
$format,
$structuredOutput,
$formatterOptions
);
return 0;
}
/**
* Description
* @param OutputInterface $output
* @param int $status
* @param string $structuredOutput
* @param mixed $originalResult
* @return type
*/
protected function writeErrorMessage($output, $status, $structuredOutput, $originalResult)
{
if (isset($this->displayErrorFunction)) {
call_user_func($this->displayErrorFunction, $output, $structuredOutput, $status, $originalResult);
} else {
$this->writeCommandOutput($output, $structuredOutput);
}
return $status;
}
/**
* If the result object is a string, then print it.
*/
protected function writeCommandOutput(
OutputInterface $output,
$structuredOutput
) {
// If there is no formatter, we will print strings,
// but can do no more than that.
if (is_string($structuredOutput)) {
$output->writeln($structuredOutput);
}
return 0;
}
/**
* If a status code was set, then return it; otherwise,
* presume success.
*/
protected function interpretStatusCode($status)
{
if (isset($status)) {
return $status;
}
return 0;
}
}
<?php
namespace Consolidation\AnnotatedCommand;
/**
* If an annotated command method encounters an error, then it
* should either throw an exception, or return a result object
* that implements ExitCodeInterface.
*/
interface ExitCodeInterface
{
public function getExitCode();
}
<?php
namespace Consolidation\AnnotatedCommand\Hooks;
/**
* Alter the result of a command after it has been processed.
* An alter result interface isa process result interface.
*
* @see HookManager::addAlterResult()
*/
interface AlterResultInterface extends ProcessResultInterface
{
}
<?php
namespace Consolidation\AnnotatedCommand\Hooks;
/**
* Extract Output hooks are used to select the particular
* data elements of the result that should be printed as
* the command output -- perhaps after being formatted.
*
* @see HookManager::addOutputExtractor()
*/
interface ExtractOutputInterface
{
public function extractOutput($result);
}
<?php
namespace Consolidation\AnnotatedCommand\Hooks;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\ConsoleEvents;
use Symfony\Component\Console\Event\ConsoleCommandEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Consolidation\AnnotatedCommand\ExitCodeInterface;
use Consolidation\AnnotatedCommand\OutputDataInterface;
use Consolidation\AnnotatedCommand\AnnotationData;
use Consolidation\AnnotatedCommand\CommandData;
use Consolidation\AnnotatedCommand\CommandError;
/**
* Manage named callback hooks
*/
class HookManager implements EventSubscriberInterface
{
protected $hooks = [];
/** var CommandInfo[] */
protected $hookOptions = [];
const PRE_COMMAND_EVENT = 'pre-command-event';
const COMMAND_EVENT = 'command-event';
const POST_COMMAND_EVENT = 'post-command-event';
const PRE_OPTION_HOOK = 'pre-option';
const OPTION_HOOK = 'option';
const POST_OPTION_HOOK = 'post-option';
const PRE_INITIALIZE = 'pre-init';
const INITIALIZE = 'init';
const POST_INITIALIZE = 'post-init';
const PRE_INTERACT = 'pre-interact';
const INTERACT = 'interact';
const POST_INTERACT = 'post-interact';
const PRE_ARGUMENT_VALIDATOR = 'pre-validate';
const ARGUMENT_VALIDATOR = 'validate';
const POST_ARGUMENT_VALIDATOR = 'post-validate';
const PRE_COMMAND_HOOK = 'pre-command';
const COMMAND_HOOK = 'command';
const POST_COMMAND_HOOK = 'post-command';
const PRE_PROCESS_RESULT = 'pre-process';
const PROCESS_RESULT = 'process';
const POST_PROCESS_RESULT = 'post-process';
const PRE_ALTER_RESULT = 'pre-alter';
const ALTER_RESULT = 'alter';
const POST_ALTER_RESULT = 'post-alter';
const STATUS_DETERMINER = 'status';
const EXTRACT_OUTPUT = 'extract';
public function __construct()
{
}
public function getAllHooks()
{
return $this->hooks;
}
/**
* Add a hook
*
* @param mixed $callback The callback function to call
* @param string $hook The name of the hook to add
* @param string $name The name of the command to hook
* ('*' for all)
*/
public function add(callable $callback, $hook, $name = '*')
{
if (empty($name)) {
$name = static::getClassNameFromCallback($callback);
}
$this->hooks[$name][$hook][] = $callback;
return $this;
}
public function recordHookOptions($commandInfo, $name)
{
$this->hookOptions[$name][] = $commandInfo;
return $this;
}
public static function getNames($command, $callback)
{
return array_filter(
array_merge(
static::getNamesUsingCommands($command),
[static::getClassNameFromCallback($callback)]
)
);
}
protected static function getNamesUsingCommands($command)
{
return array_merge(
[$command->getName()],
$command->getAliases()
);
}
/**
* If a command hook does not specify any particular command
* name that it should be attached to, then it will be applied
* to every command that is defined in the same class as the hook.
* This is controlled by using the namespace + class name of
* the implementing class of the callback hook.
*/
protected static function getClassNameFromCallback($callback)
{
if (!is_array($callback)) {
return '';
}
$reflectionClass = new \ReflectionClass($callback[0]);
return $reflectionClass->getName();
}
/**
* Add an configuration provider hook
*
* @param type InitializeHookInterface $provider
* @param type $name The name of the command to hook
* ('*' for all)
*/
public function addInitializeHook(InitializeHookInterface $initializeHook, $name = '*')
{
$this->hooks[$name][self::INITIALIZE][] = $initializeHook;
return $this;
}
/**
* Add an option hook
*
* @param type ValidatorInterface $validator
* @param type $name The name of the command to hook
* ('*' for all)
*/
public function addOptionHook(OptionHookInterface $interactor, $name = '*')
{
$this->hooks[$name][self::INTERACT][] = $interactor;
return $this;
}
/**
* Add an interact hook
*
* @param type ValidatorInterface $validator
* @param type $name The name of the command to hook
* ('*' for all)
*/
public function addInteractor(InteractorInterface $interactor, $name = '*')
{
$this->hooks[$name][self::INTERACT][] = $interactor;
return $this;
}
/**
* Add a pre-validator hook
*
* @param type ValidatorInterface $validator
* @param type $name The name of the command to hook
* ('*' for all)
*/
public function addPreValidator(ValidatorInterface $validator, $name = '*')
{
$this->hooks[$name][self::PRE_ARGUMENT_VALIDATOR][] = $validator;
return $this;
}
/**
* Add a validator hook
*
* @param type ValidatorInterface $validator
* @param type $name The name of the command to hook
* ('*' for all)
*/
public function addValidator(ValidatorInterface $validator, $name = '*')
{
$this->hooks[$name][self::ARGUMENT_VALIDATOR][] = $validator;
return $this;
}
/**
* Add a pre-command hook. This is the same as a validator hook, except
* that it will run after all of the post-validator hooks.
*
* @param type ValidatorInterface $preCommand
* @param type $name The name of the command to hook
* ('*' for all)
*/
public function addPreCommandHook(ValidatorInterface $preCommand, $name = '*')
{
$this->hooks[$name][self::PRE_COMMAND_HOOK][] = $preCommand;
return $this;
}
/**
* Add a post-command hook. This is the same as a pre-process hook,
* except that it will run before the first pre-process hook.
*
* @param type ProcessResultInterface $postCommand
* @param type $name The name of the command to hook
* ('*' for all)
*/
public function addPostCommandHook(ProcessResultInterface $postCommand, $name = '*')
{
$this->hooks[$name][self::POST_COMMAND_HOOK][] = $postCommand;
return $this;
}
/**
* Add a result processor.
*
* @param type ProcessResultInterface $resultProcessor
* @param type $name The name of the command to hook
* ('*' for all)
*/
public function addResultProcessor(ProcessResultInterface $resultProcessor, $name = '*')
{
$this->hooks[$name][self::PROCESS_RESULT][] = $resultProcessor;
return $this;
}
/**
* Add a result alterer. After a result is processed
* by a result processor, an alter hook may be used
* to convert the result from one form to another.
*
* @param type AlterResultInterface $resultAlterer
* @param type $name The name of the command to hook
* ('*' for all)
*/
public function addAlterResult(AlterResultInterface $resultAlterer, $name = '*')
{
$this->hooks[$name][self::ALTER_RESULT][] = $resultAlterer;
return $this;
}
/**
* Add a status determiner. Usually, a command should return
* an integer on error, or a result object on success (which
* implies a status code of zero). If a result contains the
* status code in some other field, then a status determiner
* can be used to call the appropriate accessor method to
* determine the status code. This is usually not necessary,
* though; a command that fails may return a CommandError
* object, which contains a status code and a result message
* to display.
* @see CommandError::getExitCode()
*
* @param type StatusDeterminerInterface $statusDeterminer
* @param type $name The name of the command to hook
* ('*' for all)
*/
public function addStatusDeterminer(StatusDeterminerInterface $statusDeterminer, $name = '*')
{
$this->hooks[$name][self::STATUS_DETERMINER][] = $statusDeterminer;
return $this;
}
/**
* Add an output extractor. If a command returns an object
* object, by default it is passed directly to the output
* formatter (if in use) for rendering. If the result object
* contains more information than just the data to render, though,
* then an output extractor can be used to call the appopriate
* accessor method of the result object to get the data to
* rendered. This is usually not necessary, though; it is preferable
* to have complex result objects implement the OutputDataInterface.
* @see OutputDataInterface::getOutputData()
*
* @param type ExtractOutputInterface $outputExtractor
* @param type $name The name of the command to hook
* ('*' for all)
*/
public function addOutputExtractor(ExtractOutputInterface $outputExtractor, $name = '*')
{
$this->hooks[$name][self::EXTRACT_OUTPUT][] = $outputExtractor;
return $this;
}
public function initializeHook(
InputInterface $input,
$names,
AnnotationData $annotationData
) {
$providers = $this->getInitializeHooks($names, $annotationData);
foreach ($providers as $provider) {
$this->callInjectConfigurationHook($provider, $input, $annotationData);
}
}
public function optionsHook(
\Consolidation\AnnotatedCommand\AnnotatedCommand $command,
$names,
AnnotationData $annotationData
) {
$optionHooks = $this->getOptionHooks($names, $annotationData);
foreach ($optionHooks as $optionHook) {
$this->callOptionHook($optionHook, $command, $annotationData);
}
$commandInfoList = $this->getHookOptionsForCommand($command);
$command->optionsHookForHookAnnotations($commandInfoList);
}
public function getHookOptionsForCommand($command)
{
$names = $this->addWildcardHooksToNames($command->getNames(), $command->getAnnotationData());
return $this->getHookOptions($names);
}
/**
* @return CommandInfo[]
*/
public function getHookOptions($names)
{
$result = [];
foreach ($names as $name) {
if (isset($this->hookOptions[$name])) {
$result = array_merge($result, $this->hookOptions[$name]);
}
}
return $result;
}
public function interact(
InputInterface $input,
OutputInterface $output,
$names,
AnnotationData $annotationData
) {
$interactors = $this->getInteractors($names, $annotationData);
foreach ($interactors as $interactor) {
$this->callInteractor($interactor, $input, $output, $annotationData);
}
}
public function validateArguments($names, CommandData $commandData)
{
$validators = $this->getValidators($names, $commandData->annotationData());
foreach ($validators as $validator) {
$validated = $this->callValidator($validator, $commandData);
if ($validated === false) {
return new CommandError();
}
if (is_object($validated)) {
return $validated;
}
}
}
/**
* Process result and decide what to do with it.
* Allow client to add transformation / interpretation
* callbacks.
*/
public function alterResult($names, $result, CommandData $commandData)
{
$processors = $this->getProcessResultHooks($names, $commandData->annotationData());
foreach ($processors as $processor) {
$result = $this->callProcessor($processor, $result, $commandData);
}
$alterers = $this->getAlterResultHooks($names, $commandData->annotationData());
foreach ($alterers as $alterer) {
$result = $this->callProcessor($alterer, $result, $commandData);
}
return $result;
}
/**
* Call all status determiners, and see if any of them
* know how to convert to a status code.
*/
public function determineStatusCode($names, $result)
{
// If the result (post-processing) is an object that
// implements ExitCodeInterface, then we will ask it
// to give us the status code.
if ($result instanceof ExitCodeInterface) {
return $result->getExitCode();
}
// If the result does not implement ExitCodeInterface,
// then we'll see if there is a determiner that can
// extract a status code from the result.
$determiners = $this->getStatusDeterminers($names);
foreach ($determiners as $determiner) {
$status = $this->callDeterminer($determiner, $result);
if (isset($status)) {
return $status;
}
}
}
/**
* Convert the result object to printable output in
* structured form.
*/
public function extractOutput($names, $result)
{
if ($result instanceof OutputDataInterface) {
return $result->getOutputData();
}
$extractors = $this->getOutputExtractors($names);
foreach ($extractors as $extractor) {
$structuredOutput = $this->callExtractor($extractor, $result);
if (isset($structuredOutput)) {
return $structuredOutput;
}
}
return $result;
}
protected function getCommandEventHooks($names)
{
return $this->getHooks(
$names,
[
self::PRE_COMMAND_EVENT,
self::COMMAND_EVENT,
self::POST_COMMAND_EVENT
]
);
}
protected function getInitializeHooks($names, AnnotationData $annotationData)
{
return $this->getHooks(
$names,
[
self::PRE_INITIALIZE,
self::INITIALIZE,
self::POST_INITIALIZE
],
$annotationData
);
}
protected function getOptionHooks($names, AnnotationData $annotationData)
{
return $this->getHooks(
$names,
[
self::PRE_OPTION_HOOK,
self::OPTION_HOOK,
self::POST_OPTION_HOOK
],
$annotationData
);
}
protected function getInteractors($names, AnnotationData $annotationData)
{
return $this->getHooks(
$names,
[
self::PRE_INTERACT,
self::INTERACT,
self::POST_INTERACT
],
$annotationData
);
}
protected function getValidators($names, AnnotationData $annotationData)
{
return $this->getHooks(
$names,
[
self::PRE_ARGUMENT_VALIDATOR,
self::ARGUMENT_VALIDATOR,
self::POST_ARGUMENT_VALIDATOR,
self::PRE_COMMAND_HOOK,
self::COMMAND_HOOK,
],
$annotationData
);
}
protected function getProcessResultHooks($names, AnnotationData $annotationData)
{
return $this->getHooks(
$names,
[
self::PRE_PROCESS_RESULT,
self::PROCESS_RESULT,
self::POST_PROCESS_RESULT
],
$annotationData
);
}
protected function getAlterResultHooks($names, AnnotationData $annotationData)
{
return $this->getHooks(
$names,
[
self::PRE_ALTER_RESULT,
self::ALTER_RESULT,
self::POST_ALTER_RESULT,
self::POST_COMMAND_HOOK,
],
$annotationData
);
}
protected function getStatusDeterminers($names)
{
return $this->getHooks(
$names,
[
self::STATUS_DETERMINER,
]
);
}
protected function getOutputExtractors($names)
{
return $this->getHooks(
$names,
[
self::EXTRACT_OUTPUT,
]
);
}
/**
* Get a set of hooks with the provided name(s). Include the
* pre- and post- hooks, and also include the global hooks ('*')
* in addition to the named hooks provided.
*
* @param string|array $names The name of the function being hooked.
* @param string[] $hooks A list of hooks (e.g. [HookManager::ALTER_RESULT])
*
* @return callable[]
*/
public function getHooks($names, $hooks, $annotationData = null)
{
return $this->get($this->addWildcardHooksToNames($names, $annotationData), $hooks);
}
protected function addWildcardHooksToNames($names, $annotationData = null)
{
$names = array_merge(
(array)$names,
($annotationData == null) ? [] : array_map(function ($item) {
return "@$item";
}, $annotationData->keys())
);
$names[] = '*';
return array_unique($names);
}
/**
* Get a set of hooks with the provided name(s).
*
* @param string|array $names The name of the function being hooked.
* @param string[] $hooks The list of hook names (e.g. [HookManager::ALTER_RESULT])
*
* @return callable[]
*/
public function get($names, $hooks)
{
$result = [];
foreach ((array)$hooks as $hook) {
foreach ((array)$names as $name) {
$result = array_merge($result, $this->getHook($name, $hook));
}
}
return $result;
}
/**
* Get a single named hook.
*
* @param string $name The name of the hooked method
* @param string $hook The specific hook name (e.g. alter)
*
* @return callable[]
*/
protected function getHook($name, $hook)
{
if (isset($this->hooks[$name][$hook])) {
return $this->hooks[$name][$hook];
}
return [];
}
protected function callInjectConfigurationHook($provider, $input, AnnotationData $annotationData)
{
if ($provider instanceof InitializeHookInterface) {
return $provider->applyConfiguration($input, $annotationData);
}
if (is_callable($provider)) {
return $provider($input, $annotationData);
}
}
protected function callOptionHook($optionHook, $command, AnnotationData $annotationData)
{
if ($optionHook instanceof OptionHookInterface) {
return $optionHook->getOptions($command, $annotationData);
}
if (is_callable($optionHook)) {
return $optionHook($command, $annotationData);
}
}
protected function callInteractor($interactor, $input, $output, AnnotationData $annotationData)
{
if ($interactor instanceof InteractorInterface) {
return $interactor->interact($input, $output, $annotationData);
}
if (is_callable($interactor)) {
return $interactor($input, $output, $annotationData);
}
}
protected function callValidator($validator, CommandData $commandData)
{
if ($validator instanceof ValidatorInterface) {
return $validator->validate($commandData);
}
if (is_callable($validator)) {
return $validator($commandData);
}
}
protected function callProcessor($processor, $result, CommandData $commandData)
{
$processed = null;
if ($processor instanceof ProcessResultInterface) {
$processed = $processor->process($result, $commandData);
}
if (is_callable($processor)) {
$processed = $processor($result, $commandData);
}
if (isset($processed)) {
return $processed;
}
return $result;
}
protected function callDeterminer($determiner, $result)
{
if ($determiner instanceof StatusDeterminerInterface) {
return $determiner->determineStatusCode($result);
}
if (is_callable($determiner)) {
return $determiner($result);
}
}
protected function callExtractor($extractor, $result)
{
if ($extractor instanceof ExtractOutputInterface) {
return $extractor->extractOutput($result);
}
if (is_callable($extractor)) {
return $extractor($result);
}
}
/**
* @param ConsoleCommandEvent $event
*/
public function callCommandEventHooks(ConsoleCommandEvent $event)
{
/* @var Command $command */
$command = $event->getCommand();
$names = [$command->getName()];
$commandEventHooks = $this->getCommandEventHooks($names);
foreach ($commandEventHooks as $commandEvent) {
if (is_callable($commandEvent)) {
$commandEvent($event);
}
}
}
public function findAndAddHookOptions($command)
{
if (!$command instanceof \Consolidation\AnnotatedCommand\AnnotatedCommand) {
return;
}
$command->optionsHook();
}
/**
* @{@inheritdoc}
*/
public static function getSubscribedEvents()
{
return [ConsoleEvents::COMMAND => 'callCommandEventHooks'];
}
}
<?php
namespace Consolidation\AnnotatedCommand\Hooks;
use Consolidation\AnnotatedCommand\AnnotationData;
use Symfony\Component\Console\Input\InputInterface;
/**
* Non-interactively (e.g. via configuration files) apply configuration values to the Input object.
*
* @see HookManager::addInitializeHook()
*/
interface InitializeHookInterface
{
public function initialize(InputInterface $input, Annotation $annotationData);
}
<?php
namespace Consolidation\AnnotatedCommand\Hooks;
use Consolidation\AnnotatedCommand\AnnotationData;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Interactively supply values for missing required arguments for
* the current command. Note that this hook is not called if
* the --no-interaction flag is set.
*
* @see HookManager::addInteractor()
*/
interface InteractorInterface
{
public function interact(InputInterface $input, OutputInterface $output, AnnotationData $annotationData);
}
<?php
namespace Consolidation\AnnotatedCommand\Hooks;
use Consolidation\AnnotatedCommand\AnnotationData;
use Symfony\Component\Console\Command\Command;
/**
* Add options to a command.
*
* @see HookManager::addOptionHook()
* @see AnnotatedCommandFactory::addListener()
*/
interface OptionHookInterface
{
public function getOptions(Command $command, AnnotationData $annotationData);
}
<?php
namespace Consolidation\AnnotatedCommand\Hooks;
use Consolidation\AnnotatedCommand\CommandData;
/**
* A result processor takes a result object, processes it, and
* returns another result object. For example, if a result object
* represents a 'task', then a task-runner hook could run the
* task and return the result from that execution.
*
* @see HookManager::addResultProcessor()
*/
interface ProcessResultInterface
{
/**
* After a command has executed, if the result is something
* that needs to be processed, e.g. a collection of tasks to
* run, then execute it and return the new result.
*
* @param mixed $result Result to (potentially) be processed
* @param CommandData $commandData Reference to commandline arguments and options
*
* @return mixed $result
*/
public function process($result, CommandData $commandData);
}
<?php
namespace Consolidation\AnnotatedCommand\Hooks;
/**
* A StatusDeterminer maps from a result to a status exit code.
*
* @see HookManager::addStatusDeterminer()
*/
interface StatusDeterminerInterface
{
/**
* Convert a result object into a status code, if
* possible. Return null if the result object is unknown.
*
* @return null|integer
*/
public function determineStatusCode($result);
}
<?php
namespace Consolidation\AnnotatedCommand\Hooks;
use Consolidation\AnnotatedCommand\CommandData;
/**
* Validate the arguments for the current command.
*
* @see HookManager::addValidator()
*/
interface ValidatorInterface
{
public function validate(CommandData $commandData);
}
<?php
namespace Consolidation\AnnotatedCommand\Options;
use Consolidation\AnnotatedCommand\AnnotatedCommand;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\ConsoleEvents;
use Symfony\Component\Console\Event\ConsoleCommandEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* AlterOptionsCommandEvent is a subscriber to the Command Event
* that looks up any additional options (e.g. from an OPTION_HOOK)
* that should be added to the command. Options need to be added
* in two circumstances:
*
* 1. When 'help' for the command is called, so that the additional
* command options may be listed in the command description.
*
* 2. When the command itself is called, so that option validation
* may be done.
*
* We defer the addition of options until these times so that we
* do not invoke the option hooks for every command on every run
* of the program, and so that we do not need to defer the addition
* of all of the application hooks until after all of the application
* commands have been added. (Hooks may appear in the same command files
* as command implementations; applications may support command file
* plug-ins, and hooks may add options to commands defined in other
* commandfiles.)
*/
class AlterOptionsCommandEvent implements EventSubscriberInterface
{
/** var Application */
protected $application;
public function __construct(Application $application)
{
$this->application = $application;
}
/**
* @param ConsoleCommandEvent $event
*/
public function alterCommandOptions(ConsoleCommandEvent $event)
{
/* @var Command $command */
$command = $event->getCommand();
$input = $event->getInput();
if ($command->getName() == 'help') {
// Symfony 3.x prepares $input for us; Symfony 2.x, on the other
// hand, passes it in prior to binding with the command definition,
// so we have to go to a little extra work. It may be inadvisable
// to do these steps for commands other than 'help'.
if (!$input->hasArgument('command_name')) {
$command->ignoreValidationErrors();
$command->mergeApplicationDefinition();
$input->bind($command->getDefinition());
}
$nameOfCommandToDescribe = $event->getInput()->getArgument('command_name');
$commandToDescribe = $this->application->find($nameOfCommandToDescribe);
$this->findAndAddHookOptions($commandToDescribe);
} else {
$this->findAndAddHookOptions($command);
}
}
public function findAndAddHookOptions($command)
{
if (!$command instanceof AnnotatedCommand) {
return;
}
$command->optionsHook();
}
/**
* @{@inheritdoc}
*/
public static function getSubscribedEvents()
{
return [ConsoleEvents::COMMAND => 'alterCommandOptions'];
}
}
<?php
namespace Consolidation\AnnotatedCommand\Options;
use Consolidation\AnnotatedCommand\Parser\CommandInfo;
use Symfony\Component\Console\Input\InputOption;
/**
* Option providers can add options to commands based on the annotations
* present in a command. For example, a command that specifies @fields
* will automatically be given --format and --fields options.
*
* @see AnnotatedCommandFactory::addListener()
* @see HookManager::addOptionHook()
*/
interface AutomaticOptionsProviderInterface
{
/**
* @return InputOption[]
*/
public function automaticOptions(CommandInfo $commandInfo);
}
<?php
namespace Consolidation\AnnotatedCommand;
/**
* If an annotated command method returns an object that
* implements OutputDataInterface, then the getOutputData()
* method is used to fetch the output to print from the
* result object.
*/
interface OutputDataInterface
{
public function getOutputData();
}
<?php
namespace Consolidation\AnnotatedCommand\Parser;
use Symfony\Component\Console\Input\InputOption;
use Consolidation\AnnotatedCommand\Parser\Internal\CommandDocBlockParser;
use Consolidation\AnnotatedCommand\Parser\Internal\CommandDocBlockParserFactory;
use Consolidation\AnnotatedCommand\AnnotationData;
/**
* Given a class and method name, parse the annotations in the
* DocBlock comment, and provide accessor methods for all of
* the elements that are needed to create a Symfony Console Command.
*
* Note that the name of this class is now somewhat of a misnomer,
* as we now use it to hold annotation data for hooks as well as commands.
* It would probably be better to rename this to MethodInfo at some point.
*/
class CommandInfo
{
/**
* @var \ReflectionMethod
*/
protected $reflection;
/**
* @var boolean
* @var string
*/
protected $docBlockIsParsed;
/**
* @var string
*/
protected $name;
/**
* @var string
*/
protected $description = '';
/**
* @var string
*/
protected $help = '';
/**
* @var DefaultsWithDescriptions
*/
protected $options;
/**
* @var DefaultsWithDescriptions
*/
protected $arguments;
/**
* @var array
*/
protected $exampleUsage = [];
/**
* @var AnnotationData
*/
protected $otherAnnotations;
/**
* @var array
*/
protected $aliases = [];
/**
* @var string
*/
protected $methodName;
/**
* @var string
*/
protected $returnType;
/**
* @var string
*/
protected $optionParamName;
/**
* Create a new CommandInfo class for a particular method of a class.
*
* @param string|mixed $classNameOrInstance The name of a class, or an
* instance of it.
* @param string $methodName The name of the method to get info about.
*/
public function __construct($classNameOrInstance, $methodName)
{
$this->reflection = new \ReflectionMethod($classNameOrInstance, $methodName);
$this->methodName = $methodName;
$this->otherAnnotations = new AnnotationData();
// Set up a default name for the command from the method name.
// This can be overridden via @command or @name annotations.
$this->name = $this->convertName($this->reflection->name);
$this->options = new DefaultsWithDescriptions($this->determineOptionsFromParameters(), false);
$this->arguments = $this->determineAgumentClassifications();
// Remember the name of the last parameter, if it holds the options.
// We will use this information to ignore @param annotations for the options.
if (!empty($this->options)) {
$this->optionParamName = $this->lastParameterName();
}
}
/**
* Recover the method name provided to the constructor.
*
* @return string
*/
public function getMethodName()
{
return $this->methodName;
}
/**
* Return the primary name for this command.
*
* @return string
*/
public function getName()
{
$this->parseDocBlock();
return $this->name;
}
/**
* Set the primary name for this command.
*
* @param string $name
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
public function getReturnType()
{
$this->parseDocBlock();
return $this->returnType;
}
public function setReturnType($returnType)
{
$this->returnType = $returnType;
return $this;
}
/**
* Get any annotations included in the docblock comment for the
* implementation method of this command that are not already
* handled by the primary methods of this class.
*
* @return AnnotationData
*/
public function getRawAnnotations()
{
$this->parseDocBlock();
return $this->otherAnnotations;
}
/**
* Get any annotations included in the docblock comment,
* also including default values such as @command. We add
* in the default @command annotation late, and only in a
* copy of the annotation data because we use the existance
* of a @command to indicate that this CommandInfo is
* a command, and not a hook or anything else.
*
* @return AnnotationData
*/
public function getAnnotations()
{
return new AnnotationData(
$this->getRawAnnotations()->getArrayCopy() +
[
'command' => $this->getName(),
]
);
}
/**
* Return a specific named annotation for this command.
*
* @param string $annotation The name of the annotation.
* @return string
*/
public function getAnnotation($annotation)
{
// hasAnnotation parses the docblock
if (!$this->hasAnnotation($annotation)) {
return null;
}
return $this->otherAnnotations[$annotation];
}
/**
* Check to see if the specified annotation exists for this command.
*
* @param string $annotation The name of the annotation.
* @return boolean
*/
public function hasAnnotation($annotation)
{
$this->parseDocBlock();
return isset($this->otherAnnotations[$annotation]);
}
/**
* Save any tag that we do not explicitly recognize in the
* 'otherAnnotations' map.
*/
public function addAnnotation($name, $content)
{
$this->otherAnnotations[$name] = $content;
}
/**
* Remove an annotation that was previoudly set.
*/
public function removeAnnotation($name)
{
unset($this->otherAnnotations[$name]);
}
/**
* Get the synopsis of the command (~first line).
*
* @return string
*/
public function getDescription()
{
$this->parseDocBlock();
return $this->description;
}
/**
* Set the command description.
*
* @param string $description The description to set.
*/
public function setDescription($description)
{
$this->description = $description;
return $this;
}
/**
* Get the help text of the command (the description)
*/
public function getHelp()
{
$this->parseDocBlock();
return $this->help;
}
/**
* Set the help text for this command.
*
* @param string $help The help text.
*/
public function setHelp($help)
{
$this->help = $help;
return $this;
}
/**
* Return the list of aliases for this command.
* @return string[]
*/
public function getAliases()
{
$this->parseDocBlock();
return $this->aliases;
}
/**
* Set aliases that can be used in place of the command's primary name.
*
* @param string|string[] $aliases
*/
public function setAliases($aliases)
{
if (is_string($aliases)) {
$aliases = explode(',', static::convertListToCommaSeparated($aliases));
}
$this->aliases = array_filter($aliases);
return $this;
}
/**
* Return the examples for this command. This is @usage instead of
* @example because the later is defined by the phpdoc standard to
* be example method calls.
*
* @return string[]
*/
public function getExampleUsages()
{
$this->parseDocBlock();
return $this->exampleUsage;
}
/**
* Add an example usage for this command.
*
* @param string $usage An example of the command, including the command
* name and all of its example arguments and options.
* @param string $description An explanation of what the example does.
*/
public function setExampleUsage($usage, $description)
{
$this->exampleUsage[$usage] = $description;
return $this;
}
/**
* Return the list of refleaction parameters.
*
* @return ReflectionParameter[]
*/
public function getParameters()
{
return $this->reflection->getParameters();
}
/**
* Descriptions of commandline arguements for this command.
*
* @return DefaultsWithDescriptions
*/
public function arguments()
{
return $this->arguments;
}
/**
* Descriptions of commandline options for this command.
*
* @return DefaultsWithDescriptions
*/
public function options()
{
return $this->options;
}
/**
* Return the name of the last parameter if it holds the options.
*/
public function optionParamName()
{
return $this->optionParamName;
}
/**
* Get the inputOptions for the options associated with this CommandInfo
* object, e.g. via @option annotations, or from
* $options = ['someoption' => 'defaultvalue'] in the command method
* parameter list.
*
* @return InputOption[]
*/
public function inputOptions()
{
$explicitOptions = [];
$opts = $this->options()->getValues();
foreach ($opts as $name => $defaultValue) {
$description = $this->options()->getDescription($name);
$fullName = $name;
$shortcut = '';
if (strpos($name, '|')) {
list($fullName, $shortcut) = explode('|', $name, 2);
}
if (is_bool($defaultValue)) {
$explicitOptions[$fullName] = new InputOption($fullName, $shortcut, InputOption::VALUE_NONE, $description);
} elseif ($defaultValue === InputOption::VALUE_REQUIRED) {
$explicitOptions[$fullName] = new InputOption($fullName, $shortcut, InputOption::VALUE_REQUIRED, $description);
} else {
$explicitOptions[$fullName] = new InputOption($fullName, $shortcut, InputOption::VALUE_OPTIONAL, $description, $defaultValue);
}
}
return $explicitOptions;
}
/**
* An option might have a name such as 'silent|s'. In this
* instance, we will allow the @option or @default tag to
* reference the option only by name (e.g. 'silent' or 's'
* instead of 'silent|s').
*
* @param string $optionName
* @return string
*/
public function findMatchingOption($optionName)
{
// Exit fast if there's an exact match
if ($this->options->exists($optionName)) {
return $optionName;
}
$existingOptionName = $this->findExistingOption($optionName);
if (isset($existingOptionName)) {
return $existingOptionName;
}
return $this->findOptionAmongAlternatives($optionName);
}
/**
* @param string $optionName
* @return string
*/
protected function findOptionAmongAlternatives($optionName)
{
// Check the other direction: if the annotation contains @silent|s
// and the options array has 'silent|s'.
$checkMatching = explode('|', $optionName);
if (count($checkMatching) > 1) {
foreach ($checkMatching as $checkName) {
if ($this->options->exists($checkName)) {
$this->options->rename($checkName, $optionName);
return $optionName;
}
}
}
return $optionName;
}
/**
* @param string $optionName
* @return string|null
*/
protected function findExistingOption($optionName)
{
// Check to see if we can find the option name in an existing option,
// e.g. if the options array has 'silent|s' => false, and the annotation
// is @silent.
foreach ($this->options()->getValues() as $name => $default) {
if (in_array($optionName, explode('|', $name))) {
return $name;
}
}
}
/**
* Examine the parameters of the method for this command, and
* build a list of commandline arguements for them.
*
* @return array
*/
protected function determineAgumentClassifications()
{
$result = new DefaultsWithDescriptions();
$params = $this->reflection->getParameters();
$optionsFromParameters = $this->determineOptionsFromParameters();
if (!empty($optionsFromParameters)) {
array_pop($params);
}
foreach ($params as $param) {
$this->addParameterToResult($result, $param);
}
return $result;
}
/**
* Examine the provided parameter, and determine whether it
* is a parameter that will be filled in with a positional
* commandline argument.
*/
protected function addParameterToResult($result, $param)
{
// Commandline arguments must be strings, so ignore any
// parameter that is typehinted to any non-primative class.
if ($param->getClass() != null) {
return;
}
$result->add($param->name);
if ($param->isDefaultValueAvailable()) {
$defaultValue = $param->getDefaultValue();
if (!$this->isAssoc($defaultValue)) {
$result->setDefaultValue($param->name, $defaultValue);
}
} elseif ($param->isArray()) {
$result->setDefaultValue($param->name, []);
}
}
/**
* Examine the parameters of the method for this command, and determine
* the disposition of the options from them.
*
* @return array
*/
protected function determineOptionsFromParameters()
{
$params = $this->reflection->getParameters();
if (empty($params)) {
return [];
}
$param = end($params);
if (!$param->isDefaultValueAvailable()) {
return [];
}
if (!$this->isAssoc($param->getDefaultValue())) {
return [];
}
return $param->getDefaultValue();
}
protected function lastParameterName()
{
$params = $this->reflection->getParameters();
$param = end($params);
if (!$param) {
return '';
}
return $param->name;
}
/**
* Helper; determine if an array is associative or not. An array
* is not associative if its keys are numeric, and numbered sequentially
* from zero. All other arrays are considered to be associative.
*
* @param arrau $arr The array
* @return boolean
*/
protected function isAssoc($arr)
{
if (!is_array($arr)) {
return false;
}
return array_keys($arr) !== range(0, count($arr) - 1);
}
/**
* Convert from a method name to the corresponding command name. A
* method 'fooBar' will become 'foo:bar', and 'fooBarBazBoz' will
* become 'foo:bar-baz-boz'.
*
* @param string $camel method name.
* @return string
*/
protected function convertName($camel)
{
$splitter="-";
$camel=preg_replace('/(?!^)[[:upper:]][[:lower:]]/', '$0', preg_replace('/(?!^)[[:upper:]]+/', $splitter.'$0', $camel));
$camel = preg_replace("/$splitter/", ':', $camel, 1);
return strtolower($camel);
}
/**
* Parse the docBlock comment for this command, and set the
* fields of this class with the data thereby obtained.
*/
protected function parseDocBlock()
{
if (!$this->docBlockIsParsed) {
// The parse function will insert data from the provided method
// into this object, using our accessors.
CommandDocBlockParserFactory::parse($this, $this->reflection);
$this->docBlockIsParsed = true;
}
}
/**
* Given a list that might be 'a b c' or 'a, b, c' or 'a,b,c',
* convert the data into the last of these forms.
*/
protected static function convertListToCommaSeparated($text)
{
return preg_replace('#[ \t\n\r,]+#', ',', $text);
}
}
<?php
namespace Consolidation\AnnotatedCommand\Parser;
/**
* An associative array that maps from key to default value;
* each entry can also have a description.
*/
class DefaultsWithDescriptions
{
/**
* @var array Associative array of key : default mappings
*/
protected $values;
/**
* @var array Associative array used like a set to indicate default value
* exists for the key.
*/
protected $hasDefault;
/**
* @var array Associative array of key : description mappings
*/
protected $descriptions;
/**
* @var mixed Default value that the default value of items in
* the collection should take when not specified in the 'add' method.
*/
protected $defaultDefault;
public function __construct($values = [], $defaultDefault = null)
{
$this->values = $values;
$this->hasDefault = [];
$this->descriptions = [];
$this->defaultDefault = $defaultDefault;
}
/**
* Return just the key : default values mapping
*
* @return array
*/
public function getValues()
{
return $this->values;
}
/**
* Return true if this set of options is empty
*
* @return
*/
public function isEmpty()
{
return empty($this->values);
}
/**
* Check to see whether the speicifed key exists in the collection.
*
* @param string $key
* @return boolean
*/
public function exists($key)
{
return array_key_exists($key, $this->values);
}
/**
* Get the value of one entry.
*
* @param string $key The key of the item.
* @return string
*/
public function get($key)
{
if (array_key_exists($key, $this->values)) {
return $this->values[$key];
}
return $this->defaultDefault;
}
/**
* Get the description of one entry.
*
* @param string $key The key of the item.
* @return string
*/
public function getDescription($key)
{
if (array_key_exists($key, $this->descriptions)) {
return $this->descriptions[$key];
}
return '';
}
/**
* Add another argument to this command.
*
* @param string $key Name of the argument.
* @param string $description Help text for the argument.
* @param mixed $defaultValue The default value for the argument.
*/
public function add($key, $description = '', $defaultValue = null)
{
if (!$this->exists($key) || isset($defaultValue)) {
$this->values[$key] = isset($defaultValue) ? $defaultValue : $this->defaultDefault;
}
unset($this->descriptions[$key]);
if (!empty($description)) {
$this->descriptions[$key] = $description;
}
}
/**
* Change the default value of an entry.
*
* @param string $key
* @param mixed $defaultValue
*/
public function setDefaultValue($key, $defaultValue)
{
$this->values[$key] = $defaultValue;
$this->hasDefault[$key] = true;
return $this;
}
/**
* Check to see if the named argument definitively has a default value.
*
* @param string $key
* @return bool
*/
public function hasDefault($key)
{
return array_key_exists($key, $this->hasDefault);
}
/**
* Remove an entry
*
* @param string $key The entry to remove
*/
public function clear($key)
{
unset($this->values[$key]);
unset($this->descriptions[$key]);
}
/**
* Rename an existing option to something else.
*/
public function rename($oldName, $newName)
{
$this->add($newName, $this->getDescription($oldName), $this->get($oldName));
$this->clear($oldName);
}
}
<?php
namespace Consolidation\AnnotatedCommand\Parser\Internal;
use Consolidation\AnnotatedCommand\Parser\CommandInfo;
use Consolidation\AnnotatedCommand\Parser\DefaultsWithDescriptions;
/**
* Given a class and method name, parse the annotations in the
* DocBlock comment, and provide accessor methods for all of
* the elements that are needed to create an annotated Command.
*/
abstract class AbstractCommandDocBlockParser
{
/**
* @var CommandInfo
*/
protected $commandInfo;
/**
* @var \ReflectionMethod
*/
protected $reflection;
/**
* @var array
*/
protected $tagProcessors = [
'command' => 'processCommandTag',
'name' => 'processCommandTag',
'arg' => 'processArgumentTag',
'param' => 'processParamTag',
'return' => 'processReturnTag',
'option' => 'processOptionTag',
'default' => 'processDefaultTag',
'aliases' => 'processAliases',
'usage' => 'processUsageTag',
'description' => 'processAlternateDescriptionTag',
'desc' => 'processAlternateDescriptionTag',
];
public function __construct(CommandInfo $commandInfo, \ReflectionMethod $reflection)
{
$this->commandInfo = $commandInfo;
$this->reflection = $reflection;
}
protected function processAllTags($phpdoc)
{
// Iterate over all of the tags, and process them as necessary.
foreach ($phpdoc->getTags() as $tag) {
$processFn = [$this, 'processGenericTag'];
if (array_key_exists($tag->getName(), $this->tagProcessors)) {
$processFn = [$this, $this->tagProcessors[$tag->getName()]];
}
$processFn($tag);
}
}
abstract protected function getTagContents($tag);
/**
* Parse the docBlock comment for this command, and set the
* fields of this class with the data thereby obtained.
*/
abstract public function parse();
/**
* Save any tag that we do not explicitly recognize in the
* 'otherAnnotations' map.
*/
protected function processGenericTag($tag)
{
$this->commandInfo->addAnnotation($tag->getName(), $this->getTagContents($tag));
}
/**
* Set the name of the command from a @command or @name annotation.
*/
protected function processCommandTag($tag)
{
$commandName = $this->getTagContents($tag);
$this->commandInfo->setName($commandName);
// We also store the name in the 'other annotations' so that is is
// possible to determine if the method had a @command annotation.
$this->commandInfo->addAnnotation($tag->getName(), $commandName);
}
/**
* The @description and @desc annotations may be used in
* place of the synopsis (which we call 'description').
* This is discouraged.
*
* @deprecated
*/
protected function processAlternateDescriptionTag($tag)
{
$this->commandInfo->setDescription($this->getTagContents($tag));
}
/**
* Store the data from a @arg annotation in our argument descriptions.
*/
protected function processArgumentTag($tag)
{
if (!$this->pregMatchNameAndDescription((string)$tag->getDescription(), $match)) {
return;
}
$this->addOptionOrArgumentTag($tag, $this->commandInfo->arguments(), $match);
}
/**
* Store the data from an @option annotation in our option descriptions.
*/
protected function processOptionTag($tag)
{
if (!$this->pregMatchOptionNameAndDescription((string)$tag->getDescription(), $match)) {
return;
}
$this->addOptionOrArgumentTag($tag, $this->commandInfo->options(), $match);
}
protected function addOptionOrArgumentTag($tag, DefaultsWithDescriptions $set, $nameAndDescription)
{
$variableName = $this->commandInfo->findMatchingOption($nameAndDescription['name']);
$desc = $nameAndDescription['description'];
$description = static::removeLineBreaks($desc);
$set->add($variableName, $description);
}
/**
* Store the data from a @default annotation in our argument or option store,
* as appropriate.
*/
protected function processDefaultTag($tag)
{
if (!$this->pregMatchNameAndDescription((string)$tag->getDescription(), $match)) {
return;
}
$variableName = $match['name'];
$defaultValue = $this->interpretDefaultValue($match['description']);
if ($this->commandInfo->arguments()->exists($variableName)) {
$this->commandInfo->arguments()->setDefaultValue($variableName, $defaultValue);
return;
}
$variableName = $this->commandInfo->findMatchingOption($variableName);
if ($this->commandInfo->options()->exists($variableName)) {
$this->commandInfo->options()->setDefaultValue($variableName, $defaultValue);
}
}
/**
* Store the data from a @usage annotation in our example usage list.
*/
protected function processUsageTag($tag)
{
$lines = explode("\n", $this->getTagContents($tag));
$usage = array_shift($lines);
$description = static::removeLineBreaks(implode("\n", $lines));
$this->commandInfo->setExampleUsage($usage, $description);
}
/**
* Process the comma-separated list of aliases
*/
protected function processAliases($tag)
{
$this->commandInfo->setAliases((string)$tag->getDescription());
}
/**
* Store the data from a @param annotation in our argument descriptions.
*/
protected function processParamTag($tag)
{
$variableName = $tag->getVariableName();
$variableName = str_replace('$', '', $variableName);
$description = static::removeLineBreaks((string)$tag->getDescription());
if ($variableName == $this->commandInfo->optionParamName()) {
return;
}
$this->commandInfo->arguments()->add($variableName, $description);
}
/**
* Store the data from a @return annotation in our argument descriptions.
*/
abstract protected function processReturnTag($tag);
protected function interpretDefaultValue($defaultValue)
{
$defaults = [
'null' => null,
'true' => true,
'false' => false,
"''" => '',
'[]' => [],
];
foreach ($defaults as $defaultName => $defaultTypedValue) {
if ($defaultValue == $defaultName) {
return $defaultTypedValue;
}
}
return $defaultValue;
}
/**
* Given a docblock description in the form "$variable description",
* return the variable name and description via the 'match' parameter.
*/
protected function pregMatchNameAndDescription($source, &$match)
{
$nameRegEx = '\\$(?P<name>[^ \t]+)[ \t]+';
$descriptionRegEx = '(?P<description>.*)';
$optionRegEx = "/{$nameRegEx}{$descriptionRegEx}/s";
return preg_match($optionRegEx, $source, $match);
}
/**
* Given a docblock description in the form "$variable description",
* return the variable name and description via the 'match' parameter.
*/
protected function pregMatchOptionNameAndDescription($source, &$match)
{
// Strip type and $ from the text before the @option name, if present.
$source = preg_replace('/^[a-zA-Z]* ?\\$/', '', $source);
$nameRegEx = '(?P<name>[^ \t]+)[ \t]+';
$descriptionRegEx = '(?P<description>.*)';
$optionRegEx = "/{$nameRegEx}{$descriptionRegEx}/s";
return preg_match($optionRegEx, $source, $match);
}
/**
* Given a list that might be 'a b c' or 'a, b, c' or 'a,b,c',
* convert the data into the last of these forms.
*/
protected static function convertListToCommaSeparated($text)
{
return preg_replace('#[ \t\n\r,]+#', ',', $text);
}
/**
* Take a multiline description and convert it into a single
* long unbroken line.
*/
protected static function removeLineBreaks($text)
{
return trim(preg_replace('#[ \t\n\r]+#', ' ', $text));
}
}
<?php
namespace Consolidation\AnnotatedCommand\Parser\Internal;
use phpDocumentor\Reflection\DocBlock;
use phpDocumentor\Reflection\DocBlock\Tag\ParamTag;
use phpDocumentor\Reflection\DocBlock\Tag\ReturnTag;
use Consolidation\AnnotatedCommand\Parser\CommandInfo;
use Consolidation\AnnotatedCommand\Parser\DefaultsWithDescriptions;
/**
* Given a class and method name, parse the annotations in the
* DocBlock comment, and provide accessor methods for all of
* the elements that are needed to create an annotated Command.
*/
class CommandDocBlockParser2 extends AbstractCommandDocBlockParser
{
/**
* Parse the docBlock comment for this command, and set the
* fields of this class with the data thereby obtained.
*/
public function parse()
{
$docblockComment = $this->reflection->getDocComment();
$phpdoc = new DocBlock($docblockComment);
// First set the description (synopsis) and help.
$this->commandInfo->setDescription((string)$phpdoc->getShortDescription());
$this->commandInfo->setHelp((string)$phpdoc->getLongDescription());
$this->processAllTags($phpdoc);
}
protected function getTagContents($tag)
{
return $tag->getContent();
}
/**
* Store the data from a @arg annotation in our argument descriptions.
*/
protected function processArgumentTag($tag)
{
if (!$this->pregMatchNameAndDescription((string)$tag->getDescription(), $match)) {
return;
}
$this->addOptionOrArgumentTag($tag, $this->commandInfo->arguments(), $match);
}
/**
* Store the data from a @param annotation in our argument descriptions.
*/
protected function processParamTag($tag)
{
if (!$tag instanceof ParamTag) {
return;
}
return parent::processParamTag($tag);
}
/**
* Store the data from a @return annotation in our argument descriptions.
*/
protected function processReturnTag($tag)
{
if (!$tag instanceof ReturnTag) {
return;
}
$this->commandInfo->setReturnType($tag->getType());
}
}
<?php
namespace Consolidation\AnnotatedCommand\Parser\Internal;
use phpDocumentor\Reflection\DocBlock\Tags\Param;
use phpDocumentor\Reflection\DocBlock\Tags\Return_;
use Consolidation\AnnotatedCommand\Parser\CommandInfo;
use Consolidation\AnnotatedCommand\Parser\DefaultsWithDescriptions;
/**
* Given a class and method name, parse the annotations in the
* DocBlock comment, and provide accessor methods for all of
* the elements that are needed to create an annotated Command.
*/
class CommandDocBlockParser3 extends AbstractCommandDocBlockParser
{
/**
* Parse the docBlock comment for this command, and set the
* fields of this class with the data thereby obtained.
*/
public function parse()
{
// DocBlockFactory::create fails if the comment is empty.
$docComment = $this->reflection->getDocComment();
if (empty($docComment)) {
return;
}
$phpdoc = $this->createDocBlock();
// First set the description (synopsis) and help.
$this->commandInfo->setDescription((string)$phpdoc->getSummary());
$this->commandInfo->setHelp((string)$phpdoc->getDescription());
$this->processAllTags($phpdoc);
}
public function createDocBlock()
{
$docBlockFactory = \phpDocumentor\Reflection\DocBlockFactory::createInstance();
$contextFactory = new \phpDocumentor\Reflection\Types\ContextFactory();
return $docBlockFactory->create(
$this->reflection,
$contextFactory->createFromReflector($this->reflection)
);
}
protected function getTagContents($tag)
{
return (string)$tag;
}
/**
* Store the data from a @param annotation in our argument descriptions.
*/
protected function processParamTag($tag)
{
if (!$tag instanceof Param) {
return;
}
return parent::processParamTag($tag);
}
/**
* Store the data from a @return annotation in our argument descriptions.
*/
protected function processReturnTag($tag)
{
if (!$tag instanceof Return_) {
return;
}
// If there is a spurrious trailing space on the return type, remove it.
$this->commandInfo->setReturnType(trim($this->getTagContents($tag)));
}
}
<?php
namespace Consolidation\AnnotatedCommand\Parser\Internal;
use Consolidation\AnnotatedCommand\Parser\CommandInfo;
/**
* Create an appropriate CommandDocBlockParser.
*/
class CommandDocBlockParserFactory
{
public static function parse(CommandInfo $commandInfo, \ReflectionMethod $reflection)
{
return static::create($commandInfo, $reflection)->parse();
}
private static function create(CommandInfo $commandInfo, \ReflectionMethod $reflection)
{
if (static::hasReflectionDocBlock3()) {
return new CommandDocBlockParser3($commandInfo, $reflection);
}
return new CommandDocBlockParser2($commandInfo, $reflection);
}
private static function hasReflectionDocBlock3()
{
return class_exists('phpDocumentor\Reflection\DocBlockFactory') && class_exists('phpDocumentor\Reflection\Types\ContextFactory');
}
}
<?php
namespace Consolidation\Log;
/**
* Additional log levels for use in Console applications.
*
* ConsoleLogLevels may be used by methods of Symfony Command
* in applications where it is known that the Consolidation logger
* is in use. These log levels provide access to the 'success'
* styled output method. Code in reusable libraries that may
* be used with a standard Psr-3 logger should aviod using these
* log levels.
*
* All ConsoleLogLevels should be interpreted as LogLevel\NOTICE.
*
* @author Greg Anderson <greg.1.anderson@greenknowe.org>
*/
class ConsoleLogLevel extends \Psr\Log\LogLevel
{
/**
* Command successfully completed some operation.
* Displayed at VERBOSITY_NORMAL.
*/
const SUCCESS = 'success';
}
<?php
namespace Consolidation\Log;
use Psr\Log\AbstractLogger;
use Psr\Log\InvalidArgumentException;
use Psr\Log\LogLevel;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\Console\Input\StringInput;
/**
* Replacement for Symfony\Component\Console\Logger\ConsoleLogger.
* Each of the different log level messages are routed through the
* corresponding SymfonyStyle formatting method. Log messages are
* always sent to stderr if the provided output object implements
* ConsoleOutputInterface.
*
* Note that this class could extend ConsoleLogger if some methods
* of that class were declared 'protected' instead of 'private'.
*
* @author Greg Anderson <greg.1.anderson@greenknowe.org>
*/
class Logger extends AbstractLogger // extends ConsoleLogger
{
/**
* @var OutputInterface
*/
protected $output;
/**
* @var OutputInterface
*/
protected $error;
/**
* @var LogOutputStylerInterface
*/
protected $outputStyler;
/**
* @var OutputInterface|SymfonyStyle|other
*/
protected $outputStreamWrapper;
protected $errorStreamWrapper;
protected $formatFunctionMap = [
LogLevel::EMERGENCY => 'error',
LogLevel::ALERT => 'error',
LogLevel::CRITICAL => 'error',
LogLevel::ERROR => 'error',
LogLevel::WARNING => 'warning',
LogLevel::NOTICE => 'note',
LogLevel::INFO => 'note',
LogLevel::DEBUG => 'note',
ConsoleLogLevel::SUCCESS => 'success',
];
/**
* @param OutputInterface $output
* @param array $verbosityLevelMap
* @param array $formatLevelMap
* @param array $formatFunctionMap
*/
public function __construct(OutputInterface $output, array $verbosityLevelMap = array(), array $formatLevelMap = array(), array $formatFunctionMap = array())
{
$this->output = $output;
$this->verbosityLevelMap = $verbosityLevelMap + $this->verbosityLevelMap;
$this->formatLevelMap = $formatLevelMap + $this->formatLevelMap;
$this->formatFunctionMap = $formatFunctionMap + $this->formatFunctionMap;
}
public function setLogOutputStyler(LogOutputStylerInterface $outputStyler, array $formatFunctionMap = array())
{
$this->outputStyler = $outputStyler;
$this->formatFunctionMap = $formatFunctionMap + $this->formatFunctionMap;
$this->outputStreamWrapper = null;
$this->errorStreamWrapper = null;
}
public function getLogOutputStyler()
{
if (!isset($this->outputStyler)) {
$this->outputStyler = new SymfonyLogOutputStyler();
}
return $this->outputStyler;
}
protected function getOutputStream()
{
return $this->output;
}
protected function getErrorStream()
{
if (!isset($this->error)) {
$output = $this->getOutputStream();
if ($output instanceof ConsoleOutputInterface) {
$output = $output->getErrorOutput();
}
$this->error = $output;
}
return $this->error;
}
public function setOutputStream($output)
{
$this->output = $output;
$this->outputStreamWrapper = null;
}
public function setErrorStream($error)
{
$this->error = $error;
$this->errorStreamWrapper = null;
}
protected function getOutputStreamWrapper()
{
if (!isset($this->outputStreamWrapper)) {
$this->outputStreamWrapper = $this->getLogOutputStyler()->createOutputWrapper($this->getOutputStream());
}
return $this->outputStreamWrapper;
}
protected function getErrorStreamWrapper()
{
if (!isset($this->errorStreamWrapper)) {
$this->errorStreamWrapper = $this->getLogOutputStyler()->createOutputWrapper($this->getErrorStream());
}
return $this->errorStreamWrapper;
}
protected function getOutputStreamForLogLevel($level)
{
// Write to the error output if necessary and available.
// Usually, loggers that log to a terminal should send
// all log messages to stderr.
if (array_key_exists($level, $this->formatLevelMap) && ($this->formatLevelMap[$level] !== self::ERROR)) {
return $this->getOutputStreamWrapper();
}
return $this->getErrorStreamWrapper();
}
/**
* {@inheritdoc}
*/
public function log($level, $message, array $context = array())
{
// We use the '_level' context variable to allow log messages
// to be logged at one level (e.g. NOTICE) and formatted at another
// level (e.g. SUCCESS). This helps in instances where we want
// to style log messages at a custom log level that might not
// be available in all loggers. If the logger does not recognize
// the log level, then it is treated like the original log level.
if (array_key_exists('_level', $context) && array_key_exists($context['_level'], $this->verbosityLevelMap)) {
$level = $context['_level'];
}
// It is a runtime error if someone logs at a log level that
// we do not recognize.
if (!isset($this->verbosityLevelMap[$level])) {
throw new InvalidArgumentException(sprintf('The log level "%s" does not exist.', $level));
}
// Write to the error output if necessary and available.
// Usually, loggers that log to a terminal should send
// all log messages to stderr.
$outputStreamWrapper = $this->getOutputStreamForLogLevel($level);
// Ignore messages that are not at the right verbosity level
if ($this->getOutputStream()->getVerbosity() >= $this->verbosityLevelMap[$level]) {
$this->doLog($outputStreamWrapper, $level, $message, $context);
}
}
/**
* Interpolate and style the message, and then send it to the log.
*/
protected function doLog($outputStreamWrapper, $level, $message, $context)
{
$formatFunction = 'log';
if (array_key_exists($level, $this->formatFunctionMap)) {
$formatFunction = $this->formatFunctionMap[$level];
}
$interpolated = $this->interpolate(
$message,
$this->getLogOutputStyler()->style($context)
);
$this->getLogOutputStyler()->$formatFunction(
$outputStreamWrapper,
$level,
$interpolated,
$context
);
}
public function success($message, array $context = array())
{
$this->log(ConsoleLogLevel::SUCCESS, $message, $context);
}
// The functions below could be eliminated if made `protected` intead
// of `private` in ConsoleLogger
const INFO = 'info';
const ERROR = 'error';
/**
* @var OutputInterface
*/
//private $output;
/**
* @var array
*/
private $verbosityLevelMap = [
LogLevel::EMERGENCY => OutputInterface::VERBOSITY_NORMAL,
LogLevel::ALERT => OutputInterface::VERBOSITY_NORMAL,
LogLevel::CRITICAL => OutputInterface::VERBOSITY_NORMAL,
LogLevel::ERROR => OutputInterface::VERBOSITY_NORMAL,
LogLevel::WARNING => OutputInterface::VERBOSITY_NORMAL,
LogLevel::NOTICE => OutputInterface::VERBOSITY_VERBOSE,
LogLevel::INFO => OutputInterface::VERBOSITY_VERY_VERBOSE,
LogLevel::DEBUG => OutputInterface::VERBOSITY_DEBUG,
ConsoleLogLevel::SUCCESS => OutputInterface::VERBOSITY_NORMAL,
];
/**
* @var array
*
* Send all log messages to stderr. Symfony should have the same default.
* See: https://en.wikipedia.org/wiki/Standard_streams
* "Standard error was added to Unix after several wasted phototypesetting runs ended with error messages being typeset instead of displayed on the user's terminal."
*
*/
private $formatLevelMap = [
LogLevel::EMERGENCY => self::ERROR,
LogLevel::ALERT => self::ERROR,
LogLevel::CRITICAL => self::ERROR,
LogLevel::ERROR => self::ERROR,
LogLevel::WARNING => self::ERROR,
LogLevel::NOTICE => self::ERROR,
LogLevel::INFO => self::ERROR,
LogLevel::DEBUG => self::ERROR,
ConsoleLogLevel::SUCCESS => self::ERROR,
];
/**
* Interpolates context values into the message placeholders.
*
* @author PHP Framework Interoperability Group
*
* @param string $message
* @param array $context
*
* @return string
*/
private function interpolate($message, array $context)
{
// build a replacement array with braces around the context keys
$replace = array();
foreach ($context as $key => $val) {
if (!is_array($val) && (!is_object($val) || method_exists($val, '__toString'))) {
$replace[sprintf('{%s}', $key)] = $val;
}
}
// interpolate replacement values into the message and return
return strtr($message, $replace);
}
}
<?php
namespace Consolidation\Log;
use Psr\Log\LogLevel;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\OutputStyle;
/**
* Styles log output based on format mappings provided in the constructor.
*
* Override for greater control.
*/
class LogOutputStyler extends UnstyledLogOutputStyler
{
const TASK_STYLE_INFO = 'fg=white;bg=cyan;options=bold';
const TASK_STYLE_SUCCESS = 'fg=white;bg=green;options=bold';
const TASK_STYLE_WARNING = 'fg=black;bg=yellow;options=bold;';
const TASK_STYLE_ERROR = 'fg=white;bg=red;options=bold';
protected $defaultStyles = [
'*' => LogLevel::INFO,
];
protected $labelStyles = [
LogLevel::EMERGENCY => self::TASK_STYLE_ERROR,
LogLevel::ALERT => self::TASK_STYLE_ERROR,
LogLevel::CRITICAL => self::TASK_STYLE_ERROR,
LogLevel::ERROR => self::TASK_STYLE_ERROR,
LogLevel::WARNING => self::TASK_STYLE_WARNING,
LogLevel::NOTICE => self::TASK_STYLE_INFO,
LogLevel::INFO => self::TASK_STYLE_INFO,
LogLevel::DEBUG => self::TASK_STYLE_INFO,
ConsoleLogLevel::SUCCESS => self::TASK_STYLE_SUCCESS,
];
protected $messageStyles = [
LogLevel::EMERGENCY => self::TASK_STYLE_ERROR,
LogLevel::ALERT => self::TASK_STYLE_ERROR,
LogLevel::CRITICAL => self::TASK_STYLE_ERROR,
LogLevel::ERROR => self::TASK_STYLE_ERROR,
LogLevel::WARNING => '',
LogLevel::NOTICE => '',
LogLevel::INFO => '',
LogLevel::DEBUG => '',
ConsoleLogLevel::SUCCESS => '',
];
public function __construct($labelStyles = [], $messageStyles = [])
{
$this->labelStyles = $labelStyles + $this->labelStyles;
$this->messageStyles = $messageStyles + $this->messageStyles;
}
/**
* {@inheritdoc}
*/
public function defaultStyles()
{
return $this->defaultStyles;
}
/**
* {@inheritdoc}
*/
public function style($context)
{
$context += ['_style' => []];
$context['_style'] += $this->defaultStyles();
foreach ($context as $key => $value) {
$styleKey = $key;
if (!isset($context['_style'][$styleKey])) {
$styleKey = '*';
}
if (is_string($value) && isset($context['_style'][$styleKey])) {
$style = $context['_style'][$styleKey];
$context[$key] = $this->wrapFormatString($context[$key], $style);
}
}
return $context;
}
/**
* Wrap a string in a format element.
*/
protected function wrapFormatString($string, $style)
{
if ($style) {
return "<{$style}>$string</>";
}
return $string;
}
/**
* Look up the label and message styles for the specified log level,
* and use the log level as the label for the log message.
*/
protected function formatMessageByLevel($level, $message, $context)
{
$label = $level;
return $this->formatMessage($label, $message, $context, $this->labelStyles[$level], $this->messageStyles[$level]);
}
/**
* Apply styling with the provided label and message styles.
*/
protected function formatMessage($label, $message, $context, $labelStyle, $messageStyle = '')
{
if (!empty($messageStyle)) {
$message = $this->wrapFormatString(" $message ", $messageStyle);
}
if (!empty($label)) {
$message = ' ' . $this->wrapFormatString("[$label]", $labelStyle) . ' ' . $message;
}
return $message;
}
}
<?php
namespace Consolidation\Log;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Allow a log message to by styled.
*
* Styling happens in two phases:
*
* 1. Prior to message interpolation, context variables are styled
* via the 'style()' method, using styles provided in the context
* under the '_style' key, and also using styles provided by the
* 'defaultStyles()' method.
*
* @see Symfony\Component\Console\Logger\ConsoleLogger::interpolate()
*
* 2. After message interpolation, an appropriate method based on the
* log level will be called. StyledConsoleLogger::$formatFunctionMap
* is used to map the LogLevel to a LogOutputStylerInterface method to call.
*
* It is possible to select the exact class to use as the log styler
* in the constructor of StyledConsoleLogger, and the mapping from
* LogLevel to format function can also be extended. It is possible to
* add new format methods not defined here, if desired, so long as
* any method named in the format function map is implemented by the
* selected log styler class.
*/
interface LogOutputStylerInterface
{
const STYLE_CONTEXT_KEY = '_style';
/**
* Return an array of default styles to use in an application.
* The key of the style is the variable name that the style
* should be applied to (or '*' to match all variables that have
* no specific style set), and the value is the contents of the
* Symfony style tag to wrap around the variable value.
*
* Example:
* message: 'Running {command}'
* context: ['command' => 'pwd']
* default styles: ['*' => 'info']
* result: 'Running <info>pwd</>'
*/
public function defaultStyles();
/**
* Apply styles specified in the STYLE_CONTEXT_KEY context variable to
* the other named variables stored in the context. The styles from
* the context are unioned with the default styles.
*/
public function style($context);
/**
* Create a wrapper object for the output stream. If this styler
* does not require an output wrapper, it should just return
* its $output parameter.
*/
public function createOutputWrapper(OutputInterface $output);
/**
* Print an ordinary log message, usually unstyled.
*/
public function log($output, $level, $message, $context);
/**
* Print a success message.
*/
public function success($output, $level, $message, $context);
/**
* Print an error message. Used when log level is:
* - LogLevel::EMERGENCY
* - LogLevel::ALERT
* - LogLevel::CRITICAL
* - LogLevel::ERROR
*/
public function error($output, $level, $message, $context);
/**
* Print a warning message. Used when log level is:
* - LogLevel::WARNING
*/
public function warning($output, $level, $message, $context);
/**
* Print a note. Similar to 'text', but may contain additional
* styling (e.g. the task name). Used when log level is:
* - LogLevel::NOTICE
* - LogLevel::INFO
* - LogLevel::DEBUG
*
* IMPORTANT: Symfony loggers only display LogLevel::NOTICE when the
* the verbosity level is VERBOSITY_VERBOSE, unless overridden in the
* constructor. Robo\Common\Logger emits LogLevel::NOTICE at
* VERBOSITY_NORMAL so that these messages will always be displayed.
*/
public function note($output, $level, $message, $context);
/**
* Print an error message. Not used by default by StyledConsoleLogger.
*/
public function caution($output, $level, $message, $context);
}
<?php
namespace Consolidation\Log;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\Console\Input\StringInput;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Style log messages with Symfony\Component\Console\Style\SymfonyStyle.
* No context variable styling is done.
*
* This is the appropriate styler to use if your desire is to replace
* the use of SymfonyStyle with a Psr-3 logger without changing the
* appearance of your application's output.
*/
class SymfonyLogOutputStyler implements LogOutputStylerInterface
{
public function defaultStyles()
{
return [];
}
public function style($context)
{
return $context;
}
public function createOutputWrapper(OutputInterface $output)
{
// SymfonyStyle & c. contain both input and output functions,
// but we only need the output methods here. Create a stand-in
// input object to satisfy the SymfonyStyle constructor.
return new SymfonyStyle(new StringInput(''), $output);
}
public function log($symfonyStyle, $level, $message, $context)
{
$symfonyStyle->text($message);
}
public function success($symfonyStyle, $level, $message, $context)
{
$symfonyStyle->success($message);
}
public function error($symfonyStyle, $level, $message, $context)
{
$symfonyStyle->error($message);
}
public function warning($symfonyStyle, $level, $message, $context)
{
$symfonyStyle->warning($message);
}
public function note($symfonyStyle, $level, $message, $context)
{
$symfonyStyle->note($message);
}
public function caution($symfonyStyle, $level, $message, $context)
{
$symfonyStyle->caution($message);
}
}
<?php
namespace Consolidation\Log;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\OutputStyle;
/**
* Base class that provides basic unstyled output.
*/
class UnstyledLogOutputStyler implements LogOutputStylerInterface
{
public function createOutputWrapper(OutputInterface $output)
{
return $output;
}
/**
* {@inheritdoc}
*/
public function defaultStyles()
{
return [];
}
/**
* {@inheritdoc}
*/
public function style($context)
{
return $context;
}
/**
* {@inheritdoc}
*/
protected function write($output, $message, $context)
{
$output->writeln($message);
}
/**
* {@inheritdoc}
*/
public function log($output, $level, $message, $context)
{
return $this->write($output, $this->formatMessageByLevel($level, $message, $context), $context);
}
/**
* {@inheritdoc}
*/
public function success($output, $level, $message, $context)
{
return $this->write($output, $this->formatMessageByLevel($level, $message, $context), $context);
}
/**
* {@inheritdoc}
*/
public function error($output, $level, $message, $context)
{
return $this->write($output, $this->formatMessageByLevel($level, $message, $context), $context);
}
/**
* {@inheritdoc}
*/
public function warning($output, $level, $message, $context)
{
return $this->write($output, $this->formatMessageByLevel($level, $message, $context), $context);
}
/**
* {@inheritdoc}
*/
public function note($output, $level, $message, $context)
{
return $this->write($output, $this->formatMessageByLevel($level, $message, $context), $context);
}
/**
* {@inheritdoc}
*/
public function caution($output, $level, $message, $context)
{
return $this->write($output, $this->formatMessageByLevel($level, $message, $context), $context);
}
/**
* Look up the label and message styles for the specified log level,
* and use the log level as the label for the log message.
*/
protected function formatMessageByLevel($level, $message, $context)
{
return " [$level] $message";
}
}
<?php
namespace Consolidation\OutputFormatters\Exception;
/**
* Contains some helper functions used by exceptions in this project.
*/
abstract class AbstractDataFormatException extends \Exception
{
/**
* Return a description of the data type represented by the provided parameter.
*
* @param \ReflectionClass $data The data type to describe. Note that
* \ArrayObject is used as a proxy to mean an array primitive (or an ArrayObject).
* @return string
*/
protected static function describeDataType($data)
{
if (is_array($data) || ($data instanceof \ReflectionClass)) {
if (is_array($data) || ($data->getName() == 'ArrayObject')) {
return 'an array';
}
return 'an instance of ' . $data->getName();
}
if (is_string($data)) {
return 'a string';
}
if (is_object($data)) {
return 'an instance of ' . get_class($data);
}
throw new \Exception("Undescribable data error: " . var_export($data, true));
}
protected static function describeAllowedTypes($allowedTypes)
{
if (is_array($allowedTypes) && !empty($allowedTypes)) {
if (count($allowedTypes) > 1) {
return static::describeListOfAllowedTypes($allowedTypes);
}
$allowedTypes = $allowedTypes[0];
}
return static::describeDataType($allowedTypes);
}
protected static function describeListOfAllowedTypes($allowedTypes)
{
$descriptions = [];
foreach ($allowedTypes as $oneAllowedType) {
$descriptions[] = static::describeDataType($oneAllowedType);
}
if (count($descriptions) == 2) {
return "either {$descriptions[0]} or {$descriptions[1]}";
}
$lastDescription = array_pop($descriptions);
$otherDescriptions = implode(', ', $descriptions);
return "one of $otherDescriptions or $lastDescription";
}
}
<?php
namespace Consolidation\OutputFormatters\Exception;
use Consolidation\OutputFormatters\Formatters\FormatterInterface;
/**
* Represents an incompatibility between the output data and selected formatter.
*/
class IncompatibleDataException extends AbstractDataFormatException
{
public function __construct(FormatterInterface $formatter, $data, $allowedTypes)
{
$formatterDescription = get_class($formatter);
$dataDescription = static::describeDataType($data);
$allowedTypesDescription = static::describeAllowedTypes($allowedTypes);
$message = "Data provided to $formatterDescription must be $allowedTypesDescription. Instead, $dataDescription was provided.";
parent::__construct($message, 1);
}
}
<?php
namespace Consolidation\OutputFormatters\Exception;
/**
* Represents an incompatibility between the output data and selected formatter.
*/
class InvalidFormatException extends AbstractDataFormatException
{
public function __construct($format, $data, $validFormats)
{
$dataDescription = static::describeDataType($data);
$message = "The format $format cannot be used with the data produced by this command, which was $dataDescription. Valid formats are: " . implode(',', $validFormats);
parent::__construct($message, 1);
}
}
<?php
namespace Consolidation\OutputFormatters\Exception;
/**
* Indicates that the requested format does not exist.
*/
class UnknownFieldException extends \Exception
{
public function __construct($field)
{
$message = "The requested field, '$field', is not defined.";
parent::__construct($message, 1);
}
}
<?php
namespace Consolidation\OutputFormatters\Exception;
/**
* Indicates that the requested format does not exist.
*/
class UnknownFormatException extends \Exception
{
public function __construct($format)
{
$message = "The requested format, '$format', is not available.";
parent::__construct($message, 1);
}
}
<?php
namespace Consolidation\OutputFormatters;
use Consolidation\OutputFormatters\Exception\IncompatibleDataException;
use Consolidation\OutputFormatters\Exception\InvalidFormatException;
use Consolidation\OutputFormatters\Exception\UnknownFormatException;
use Consolidation\OutputFormatters\Formatters\FormatterInterface;
use Consolidation\OutputFormatters\Formatters\RenderDataInterface;
use Consolidation\OutputFormatters\Options\FormatterOptions;
use Consolidation\OutputFormatters\Options\OverrideOptionsInterface;
use Consolidation\OutputFormatters\StructuredData\RestructureInterface;
use Consolidation\OutputFormatters\Transformations\DomToArraySimplifier;
use Consolidation\OutputFormatters\Transformations\OverrideRestructureInterface;
use Consolidation\OutputFormatters\Transformations\SimplifyToArrayInterface;
use Consolidation\OutputFormatters\Validate\ValidationInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Consolidation\OutputFormatters\StructuredData\OriginalDataInterface;
/**
* Manage a collection of formatters; return one on request.
*/
class FormatterManager
{
/** var FormatterInterface[] */
protected $formatters = [];
/** var SimplifyToArrayInterface[] */
protected $arraySimplifiers = [];
public function __construct()
{
}
public function addDefaultFormatters()
{
$defaultFormatters = [
'string' => '\Consolidation\OutputFormatters\Formatters\StringFormatter',
'yaml' => '\Consolidation\OutputFormatters\Formatters\YamlFormatter',
'xml' => '\Consolidation\OutputFormatters\Formatters\XmlFormatter',
'json' => '\Consolidation\OutputFormatters\Formatters\JsonFormatter',
'print-r' => '\Consolidation\OutputFormatters\Formatters\PrintRFormatter',
'php' => '\Consolidation\OutputFormatters\Formatters\SerializeFormatter',
'var_export' => '\Consolidation\OutputFormatters\Formatters\VarExportFormatter',
'list' => '\Consolidation\OutputFormatters\Formatters\ListFormatter',
'csv' => '\Consolidation\OutputFormatters\Formatters\CsvFormatter',
'tsv' => '\Consolidation\OutputFormatters\Formatters\TsvFormatter',
'table' => '\Consolidation\OutputFormatters\Formatters\TableFormatter',
'sections' => '\Consolidation\OutputFormatters\Formatters\SectionsFormatter',
];
foreach ($defaultFormatters as $id => $formatterClassname) {
$formatter = new $formatterClassname;
$this->addFormatter($id, $formatter);
}
$this->addFormatter('', $this->formatters['string']);
}
public function addDefaultSimplifiers()
{
// Add our default array simplifier (DOMDocument to array)
$this->addSimplifier(new DomToArraySimplifier());
}
/**
* Add a formatter
*
* @param string $key the identifier of the formatter to add
* @param string $formatter the class name of the formatter to add
* @return FormatterManager
*/
public function addFormatter($key, FormatterInterface $formatter)
{
$this->formatters[$key] = $formatter;
return $this;
}
/**
* Add a simplifier
*
* @param SimplifyToArrayInterface $simplifier the array simplifier to add
* @return FormatterManager
*/
public function addSimplifier(SimplifyToArrayInterface $simplifier)
{
$this->arraySimplifiers[] = $simplifier;
return $this;
}
/**
* Return a set of InputOption based on the annotations of a command.
* @param FormatterOptions $options
* @return InputOption[]
*/
public function automaticOptions(FormatterOptions $options, $dataType)
{
$automaticOptions = [];
// At the moment, we only support automatic options for --format
// and --fields, so exit if the command returns no data.
if (!isset($dataType)) {
return [];
}
$validFormats = $this->validFormats($dataType);
if (empty($validFormats)) {
return [];
}
$availableFields = $options->get(FormatterOptions::FIELD_LABELS);
$hasDefaultStringField = $options->get(FormatterOptions::DEFAULT_STRING_FIELD);
$defaultFormat = $hasDefaultStringField ? 'string' : ($availableFields ? 'table' : 'yaml');
if (count($validFormats) > 1) {
// Make an input option for --format
$description = 'Format the result data. Available formats: ' . implode(',', $validFormats);
$automaticOptions[FormatterOptions::FORMAT] = new InputOption(FormatterOptions::FORMAT, '', InputOption::VALUE_OPTIONAL, $description, $defaultFormat);
}
if ($availableFields) {
$defaultFields = $options->get(FormatterOptions::DEFAULT_FIELDS, [], '');
$description = 'Available fields: ' . implode(', ', $this->availableFieldsList($availableFields));
$automaticOptions[FormatterOptions::FIELDS] = new InputOption(FormatterOptions::FIELDS, '', InputOption::VALUE_OPTIONAL, $description, $defaultFields);
$automaticOptions[FormatterOptions::FIELD] = new InputOption(FormatterOptions::FIELD, '', InputOption::VALUE_OPTIONAL, "Select just one field, and force format to 'string'.", '');
}
return $automaticOptions;
}
/**
* Given a list of available fields, return a list of field descriptions.
* @return string[]
*/
protected function availableFieldsList($availableFields)
{
return array_map(
function ($key) use ($availableFields) {
return $availableFields[$key] . " ($key)";
},
array_keys($availableFields)
);
}
/**
* Return the identifiers for all valid data types that have been registered.
*
* @param mixed $dataType \ReflectionObject or other description of the produced data type
* @return array
*/
public function validFormats($dataType)
{
$validFormats = [];
foreach ($this->formatters as $formatId => $formatterName) {
$formatter = $this->getFormatter($formatId);
if (!empty($formatId) && $this->isValidFormat($formatter, $dataType)) {
$validFormats[] = $formatId;
}
}
sort($validFormats);
return $validFormats;
}
public function isValidFormat(FormatterInterface $formatter, $dataType)
{
if (is_array($dataType)) {
$dataType = new \ReflectionClass('\ArrayObject');
}
if (!is_object($dataType) && !class_exists($dataType)) {
return false;
}
if (!$dataType instanceof \ReflectionClass) {
$dataType = new \ReflectionClass($dataType);
}
return $this->isValidDataType($formatter, $dataType);
}
public function isValidDataType(FormatterInterface $formatter, \ReflectionClass $dataType)
{
if ($this->canSimplifyToArray($dataType)) {
if ($this->isValidFormat($formatter, [])) {
return true;
}
}
// If the formatter does not implement ValidationInterface, then
// it is presumed that the formatter only accepts arrays.
if (!$formatter instanceof ValidationInterface) {
return $dataType->isSubclassOf('ArrayObject') || ($dataType->getName() == 'ArrayObject');
}
return $formatter->isValidDataType($dataType);
}
/**
* Format and write output
*
* @param OutputInterface $output Output stream to write to
* @param string $format Data format to output in
* @param mixed $structuredOutput Data to output
* @param FormatterOptions $options Formatting options
*/
public function write(OutputInterface $output, $format, $structuredOutput, FormatterOptions $options)
{
$formatter = $this->getFormatter((string)$format);
if (!is_string($structuredOutput) && !$this->isValidFormat($formatter, $structuredOutput)) {
$validFormats = $this->validFormats($structuredOutput);
throw new InvalidFormatException((string)$format, $structuredOutput, $validFormats);
}
// Give the formatter a chance to override the options
$options = $this->overrideOptions($formatter, $structuredOutput, $options);
$structuredOutput = $this->validateAndRestructure($formatter, $structuredOutput, $options);
$formatter->write($output, $structuredOutput, $options);
}
protected function validateAndRestructure(FormatterInterface $formatter, $structuredOutput, FormatterOptions $options)
{
// Give the formatter a chance to do something with the
// raw data before it is restructured.
$overrideRestructure = $this->overrideRestructure($formatter, $structuredOutput, $options);
if ($overrideRestructure) {
return $overrideRestructure;
}
// Restructure the output data (e.g. select fields to display, etc.).
$restructuredOutput = $this->restructureData($structuredOutput, $options);
// Make sure that the provided data is in the correct format for the selected formatter.
$restructuredOutput = $this->validateData($formatter, $restructuredOutput, $options);
// Give the original data a chance to re-render the structured
// output after it has been restructured and validated.
$restructuredOutput = $this->renderData($formatter, $structuredOutput, $restructuredOutput, $options);
return $restructuredOutput;
}
/**
* Fetch the requested formatter.
*
* @param string $format Identifier for requested formatter
* @return FormatterInterface
*/
public function getFormatter($format)
{
// The client must inject at least one formatter before asking for
// any formatters; if not, we will provide all of the usual defaults
// as a convenience.
if (empty($this->formatters)) {
$this->addDefaultFormatters();
$this->addDefaultSimplifiers();
}
if (!$this->hasFormatter($format)) {
throw new UnknownFormatException($format);
}
$formatter = $this->formatters[$format];
return $formatter;
}
/**
* Test to see if the stipulated format exists
*/
public function hasFormatter($format)
{
return array_key_exists($format, $this->formatters);
}
/**
* Render the data as necessary (e.g. to select or reorder fields).
*
* @param FormatterInterface $formatter
* @param mixed $originalData
* @param mixed $restructuredData
* @param FormatterOptions $options Formatting options
* @return mixed
*/
public function renderData(FormatterInterface $formatter, $originalData, $restructuredData, FormatterOptions $options)
{
if ($formatter instanceof RenderDataInterface) {
return $formatter->renderData($originalData, $restructuredData, $options);
}
return $restructuredData;
}
/**
* Determine if the provided data is compatible with the formatter being used.
*
* @param FormatterInterface $formatter Formatter being used
* @param mixed $structuredOutput Data to validate
* @return mixed
*/
public function validateData(FormatterInterface $formatter, $structuredOutput, FormatterOptions $options)
{
// If the formatter implements ValidationInterface, then let it
// test the data and throw or return an error
if ($formatter instanceof ValidationInterface) {
return $formatter->validate($structuredOutput);
}
// If the formatter does not implement ValidationInterface, then
// it will never be passed an ArrayObject; we will always give
// it a simple array.
$structuredOutput = $this->simplifyToArray($structuredOutput, $options);
// If we could not simplify to an array, then throw an exception.
// We will never give a formatter anything other than an array
// unless it validates that it can accept the data type.
if (!is_array($structuredOutput)) {
throw new IncompatibleDataException(
$formatter,
$structuredOutput,
[]
);
}
return $structuredOutput;
}
protected function simplifyToArray($structuredOutput, FormatterOptions $options)
{
// We can do nothing unless the provided data is an object.
if (!is_object($structuredOutput)) {
return $structuredOutput;
}
// Check to see if any of the simplifiers can convert the given data
// set to an array.
$outputDataType = new \ReflectionClass($structuredOutput);
foreach ($this->arraySimplifiers as $simplifier) {
if ($simplifier->canSimplify($outputDataType)) {
$structuredOutput = $simplifier->simplifyToArray($structuredOutput, $options);
}
}
// Convert data structure back into its original form, if necessary.
if ($structuredOutput instanceof OriginalDataInterface) {
return $structuredOutput->getOriginalData();
}
// Convert \ArrayObjects to a simple array.
if ($structuredOutput instanceof \ArrayObject) {
return $structuredOutput->getArrayCopy();
}
return $structuredOutput;
}
protected function canSimplifyToArray(\ReflectionClass $structuredOutput)
{
foreach ($this->arraySimplifiers as $simplifier) {
if ($simplifier->canSimplify($structuredOutput)) {
return true;
}
}
return false;
}
/**
* Restructure the data as necessary (e.g. to select or reorder fields).
*
* @param mixed $structuredOutput
* @param FormatterOptions $options
* @return mixed
*/
public function restructureData($structuredOutput, FormatterOptions $options)
{
if ($structuredOutput instanceof RestructureInterface) {
return $structuredOutput->restructure($options);
}
return $structuredOutput;
}
/**
* Allow the formatter access to the raw structured data prior
* to restructuring. For example, the 'list' formatter may wish
* to display the row keys when provided table output. If this
* function returns a result that does not evaluate to 'false',
* then that result will be used as-is, and restructuring and
* validation will not occur.
*
* @param mixed $structuredOutput
* @param FormatterOptions $options
* @return mixed
*/
public function overrideRestructure(FormatterInterface $formatter, $structuredOutput, FormatterOptions $options)
{
if ($formatter instanceof OverrideRestructureInterface) {
return $formatter->overrideRestructure($structuredOutput, $options);
}
}
/**
* Allow the formatter to mess with the configuration options before any
* transformations et. al. get underway.
* @param FormatterInterface $formatter
* @param mixed $structuredOutput
* @param FormatterOptions $options
* @return FormatterOptions
*/
public function overrideOptions(FormatterInterface $formatter, $structuredOutput, FormatterOptions $options)
{
if ($formatter instanceof OverrideOptionsInterface) {
return $formatter->overrideOptions($structuredOutput, $options);
}
return $options;
}
}
<?php
namespace Consolidation\OutputFormatters\Formatters;
use Consolidation\OutputFormatters\Validate\ValidDataTypesInterface;
use Consolidation\OutputFormatters\Options\FormatterOptions;
use Consolidation\OutputFormatters\Validate\ValidDataTypesTrait;
use Consolidation\OutputFormatters\Transformations\TableTransformation;
use Consolidation\OutputFormatters\Exception\IncompatibleDataException;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Comma-separated value formatters
*
* Display the provided structured data in a comma-separated list. If
* there are multiple records provided, then they will be printed
* one per line. The primary data types accepted are RowsOfFields and
* PropertyList. The later behaves exactly like the former, save for
* the fact that it contains but a single row. This formmatter can also
* accept a PHP array; this is also interpreted as a single-row of data
* with no header.
*/
class CsvFormatter implements FormatterInterface, ValidDataTypesInterface, RenderDataInterface
{
use ValidDataTypesTrait;
use RenderTableDataTrait;
public function validDataTypes()
{
return
[
new \ReflectionClass('\Consolidation\OutputFormatters\StructuredData\RowsOfFields'),
new \ReflectionClass('\Consolidation\OutputFormatters\StructuredData\PropertyList'),
new \ReflectionClass('\ArrayObject'),
];
}
public function validate($structuredData)
{
// If the provided data was of class RowsOfFields
// or PropertyList, it will be converted into
// a TableTransformation object.
if (!is_array($structuredData) && (!$structuredData instanceof TableTransformation)) {
throw new IncompatibleDataException(
$this,
$structuredData,
$this->validDataTypes()
);
}
// If the data was provided to us as a single array, then
// convert it to a single row.
if (is_array($structuredData) && !empty($structuredData)) {
$firstRow = reset($structuredData);
if (!is_array($firstRow)) {
return [$structuredData];
}
}
return $structuredData;
}
/**
* Return default values for formatter options
* @return array
*/
protected function getDefaultFormatterOptions()
{
return [
FormatterOptions::INCLUDE_FIELD_LABELS => true,
FormatterOptions::DELIMITER => ',',
];
}
/**
* @inheritdoc
*/
public function write(OutputInterface $output, $data, FormatterOptions $options)
{
$defaults = $this->getDefaultFormatterOptions();
$includeFieldLabels = $options->get(FormatterOptions::INCLUDE_FIELD_LABELS, $defaults);
if ($includeFieldLabels && ($data instanceof TableTransformation)) {
$headers = $data->getHeaders();
$this->writeOneLine($output, $headers, $options);
}
foreach ($data as $line) {
$this->writeOneLine($output, $line, $options);
}
}
protected function writeOneLine(OutputInterface $output, $data, $options)
{
$defaults = $this->getDefaultFormatterOptions();
$delimiter = $options->get(FormatterOptions::DELIMITER, $defaults);
$output->write($this->csvEscape($data, $delimiter));
}
protected function csvEscape($data, $delimiter = ',')
{
$buffer = fopen('php://temp', 'r+');
fputcsv($buffer, $data, $delimiter);
rewind($buffer);
$csv = fgets($buffer);
fclose($buffer);
return $csv;
}
}
<?php
namespace Consolidation\OutputFormatters\Formatters;
use Consolidation\OutputFormatters\Options\FormatterOptions;
use Symfony\Component\Console\Output\OutputInterface;
interface FormatterInterface
{
/**
* Given structured data, apply appropriate
* formatting, and return a printable string.
*
* @param mixed $data Structured data to format
*
* @return string
*/
public function write(OutputInterface $output, $data, FormatterOptions $options);
}
<?php
namespace Consolidation\OutputFormatters\Formatters;
use Consolidation\OutputFormatters\Options\FormatterOptions;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Json formatter
*
* Convert an array or ArrayObject into Json.
*/
class JsonFormatter implements FormatterInterface
{
/**
* @inheritdoc
*/
public function write(OutputInterface $output, $data, FormatterOptions $options)
{
$output->writeln(json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
}
}
<?php
namespace Consolidation\OutputFormatters\Formatters;
use Consolidation\OutputFormatters\Options\FormatterOptions;
use Consolidation\OutputFormatters\StructuredData\ListDataInterface;
use Consolidation\OutputFormatters\StructuredData\RenderCellInterface;
use Consolidation\OutputFormatters\Transformations\OverrideRestructureInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Display the data in a simple list.
*
* This formatter prints a plain, unadorned list of data,
* with each data item appearing on a separate line. If you
* wish your list to contain headers, then use the table
* formatter, and wrap your data in an PropertyList.
*/
class ListFormatter implements FormatterInterface, OverrideRestructureInterface, RenderDataInterface
{
/**
* @inheritdoc
*/
public function write(OutputInterface $output, $data, FormatterOptions $options)
{
$output->writeln(implode("\n", $data));
}
/**
* @inheritdoc
*/
public function overrideRestructure($structuredOutput, FormatterOptions $options)
{
// If the structured data implements ListDataInterface,
// then we will render whatever data its 'getListData'
// method provides.
if ($structuredOutput instanceof ListDataInterface) {
return $this->renderData($structuredOutput, $structuredOutput->getListData($options), $options);
}
}
/**
* @inheritdoc
*/
public function renderData($originalData, $restructuredData, FormatterOptions $options)
{
if ($originalData instanceof RenderCellInterface) {
return $this->renderEachCell($originalData, $restructuredData, $options);
}
return $restructuredData;
}
protected function renderEachCell($originalData, $restructuredData, FormatterOptions $options)
{
foreach ($restructuredData as $key => $cellData) {
$restructuredData[$key] = $originalData->renderCell($key, $cellData, $options, $restructuredData);
}
return $restructuredData;
}
}
<?php
namespace Consolidation\OutputFormatters\Formatters;
use Consolidation\OutputFormatters\Options\FormatterOptions;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Print_r formatter
*
* Run provided date thruogh print_r.
*/
class PrintRFormatter implements FormatterInterface
{
/**
* @inheritdoc
*/
public function write(OutputInterface $output, $data, FormatterOptions $options)
{
$output->writeln(print_r($data, true));
}
}
<?php
namespace Consolidation\OutputFormatters\Formatters;
use Consolidation\OutputFormatters\Options\FormatterOptions;
interface RenderDataInterface
{
/**
* Convert the contents of the output data just before it
* is to be printed, prior to output but after restructuring
* and validation.
*
* @param mixed $originalData
* @param mixed $restructuredData
* @param FormatterOptions $options Formatting options
* @return mixed
*/
public function renderData($originalData, $restructuredData, FormatterOptions $options);
}
<?php
namespace Consolidation\OutputFormatters\Formatters;
use Consolidation\OutputFormatters\Options\FormatterOptions;
use Consolidation\OutputFormatters\StructuredData\RenderCellInterface;
trait RenderTableDataTrait
{
/**
* @inheritdoc
*/
public function renderData($originalData, $restructuredData, FormatterOptions $options)
{
if ($originalData instanceof RenderCellInterface) {
return $this->renderEachCell($originalData, $restructuredData, $options);
}
return $restructuredData;
}
protected function renderEachCell($originalData, $restructuredData, FormatterOptions $options)
{
foreach ($restructuredData as $id => $row) {
foreach ($row as $key => $cellData) {
$restructuredData[$id][$key] = $originalData->renderCell($key, $cellData, $options, $row);
}
}
return $restructuredData;
}
}
<?php
namespace Consolidation\OutputFormatters\Formatters;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Helper\Table;
use Consolidation\OutputFormatters\Validate\ValidDataTypesInterface;
use Consolidation\OutputFormatters\Options\FormatterOptions;
use Consolidation\OutputFormatters\Validate\ValidDataTypesTrait;
use Consolidation\OutputFormatters\StructuredData\TableDataInterface;
use Consolidation\OutputFormatters\Transformations\ReorderFields;
use Consolidation\OutputFormatters\Exception\IncompatibleDataException;
use Consolidation\OutputFormatters\StructuredData\PropertyList;
/**
* Display sections of data.
*
* This formatter takes data in the RowsOfFields data type.
* Each row represents one section; the data in each section
* is rendered in two columns, with the key in the first column
* and the value in the second column.
*/
class SectionsFormatter implements FormatterInterface, ValidDataTypesInterface, RenderDataInterface
{
use ValidDataTypesTrait;
use RenderTableDataTrait;
public function validDataTypes()
{
return
[
new \ReflectionClass('\Consolidation\OutputFormatters\StructuredData\RowsOfFields')
];
}
/**
* @inheritdoc
*/
public function validate($structuredData)
{
// If the provided data was of class RowsOfFields
// or PropertyList, it will be converted into
// a TableTransformation object by the restructure call.
if (!$structuredData instanceof TableDataInterface) {
throw new IncompatibleDataException(
$this,
$structuredData,
$this->validDataTypes()
);
}
return $structuredData;
}
/**
* @inheritdoc
*/
public function write(OutputInterface $output, $tableTransformer, FormatterOptions $options)
{
$table = new Table($output);
$table->setStyle('compact');
foreach ($tableTransformer as $rowid => $row) {
$rowLabel = $tableTransformer->getRowLabel($rowid);
$output->writeln('');
$output->writeln($rowLabel);
$sectionData = new PropertyList($row);
$sectionOptions = new FormatterOptions([], $options->getOptions());
$sectionTableTransformer = $sectionData->restructure($sectionOptions);
$table->setRows($sectionTableTransformer->getTableData(true));
$table->render();
}
}
}
<?php
namespace Consolidation\OutputFormatters\Formatters;
use Consolidation\OutputFormatters\Options\FormatterOptions;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Serialize formatter
*
* Run provided date thruogh serialize.
*/
class SerializeFormatter implements FormatterInterface
{
/**
* @inheritdoc
*/
public function write(OutputInterface $output, $data, FormatterOptions $options)
{
$output->writeln(serialize($data));
}
}
<?php
namespace Consolidation\OutputFormatters\Formatters;
use Consolidation\OutputFormatters\Validate\ValidationInterface;
use Consolidation\OutputFormatters\Options\OverrideOptionsInterface;
use Consolidation\OutputFormatters\Options\FormatterOptions;
use Consolidation\OutputFormatters\Validate\ValidDataTypesTrait;
use Symfony\Component\Console\Output\OutputInterface;
use Consolidation\OutputFormatters\StructuredData\RestructureInterface;
/**
* String formatter
*
* This formatter is used as the default action when no
* particular formatter is requested. It will print the
* provided data only if it is a string; if any other
* type is given, then nothing is printed.
*/
class StringFormatter implements FormatterInterface, ValidationInterface, OverrideOptionsInterface
{
/**
* All data types are acceptable.
*/
public function isValidDataType(\ReflectionClass $dataType)
{
return true;
}
/**
* @inheritdoc
*/
public function write(OutputInterface $output, $data, FormatterOptions $options)
{
if (is_string($data)) {
return $output->writeln($data);
}
return $this->reduceToSigleFieldAndWrite($output, $data, $options);
}
/**
* @inheritdoc
*/
public function overrideOptions($structuredOutput, FormatterOptions $options)
{
$defaultField = $options->get(FormatterOptions::DEFAULT_STRING_FIELD, [], '');
$userFields = $options->get(FormatterOptions::FIELDS, [FormatterOptions::FIELDS => $options->get(FormatterOptions::FIELD)]);
$optionsOverride = $options->override([]);
if (empty($userFields) && !empty($defaultField)) {
$optionsOverride->setOption(FormatterOptions::FIELDS, $defaultField);
}
return $optionsOverride;
}
/**
* If the data provided to a 'string' formatter is a table, then try
* to emit it as a TSV value.
*
* @param OutputInterface $output
* @param mixed $data
* @param FormatterOptions $options
*/
protected function reduceToSigleFieldAndWrite(OutputInterface $output, $data, FormatterOptions $options)
{
$alternateFormatter = new TsvFormatter();
try {
$data = $alternateFormatter->validate($data);
$alternateFormatter->write($output, $data, $options);
} catch (\Exception $e) {
}
}
/**
* Always validate any data, though. This format will never
* cause an error if it is selected for an incompatible data type; at
* worse, it simply does not print any data.
*/
public function validate($structuredData)
{
return $structuredData;
}
}
<?php
namespace Consolidation\OutputFormatters\Formatters;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Helper\TableStyle;
use Consolidation\OutputFormatters\Validate\ValidDataTypesInterface;
use Consolidation\OutputFormatters\Options\FormatterOptions;
use Consolidation\OutputFormatters\Validate\ValidDataTypesTrait;
use Consolidation\OutputFormatters\StructuredData\TableDataInterface;
use Consolidation\OutputFormatters\Transformations\ReorderFields;
use Consolidation\OutputFormatters\Exception\IncompatibleDataException;
/**
* Display a table of data with the Symfony Table class.
*
* This formatter takes data of either the RowsOfFields or
* PropertyList data type. Tables can be rendered with the
* rows running either vertically (the normal orientation) or
* horizontally. By default, associative lists will be displayed
* as two columns, with the key in the first column and the
* value in the second column.
*/
class TableFormatter implements FormatterInterface, ValidDataTypesInterface, RenderDataInterface
{
use ValidDataTypesTrait;
use RenderTableDataTrait;
protected $fieldLabels;
protected $defaultFields;
public function __construct()
{
}
public function validDataTypes()
{
return
[
new \ReflectionClass('\Consolidation\OutputFormatters\StructuredData\RowsOfFields'),
new \ReflectionClass('\Consolidation\OutputFormatters\StructuredData\PropertyList')
];
}
/**
* @inheritdoc
*/
public function validate($structuredData)
{
// If the provided data was of class RowsOfFields
// or PropertyList, it will be converted into
// a TableTransformation object by the restructure call.
if (!$structuredData instanceof TableDataInterface) {
throw new IncompatibleDataException(
$this,
$structuredData,
$this->validDataTypes()
);
}
return $structuredData;
}
/**
* @inheritdoc
*/
public function write(OutputInterface $output, $tableTransformer, FormatterOptions $options)
{
$defaults = [
FormatterOptions::TABLE_STYLE => 'consolidation',
FormatterOptions::INCLUDE_FIELD_LABELS => true,
];
$table = new Table($output);
// The 'consolidation' style is the same as the 'symfony-style-guide'
// style, except it maintains the colored headers used in 'default'.
$consolidationStyle = new TableStyle();
$consolidationStyle
->setHorizontalBorderChar('-')
->setVerticalBorderChar(' ')
->setCrossingChar(' ')
;
$table->setStyleDefinition('consolidation', $consolidationStyle);
$table->setStyle($options->get(FormatterOptions::TABLE_STYLE, $defaults));
$headers = $tableTransformer->getHeaders();
$isList = $tableTransformer->isList();
$includeHeaders = $options->get(FormatterOptions::INCLUDE_FIELD_LABELS, $defaults);
if ($includeHeaders && !$isList && !empty($headers)) {
$table->setHeaders($headers);
}
$table->setRows($tableTransformer->getTableData($includeHeaders && $isList));
$table->render();
}
}
<?php
namespace Consolidation\OutputFormatters\Formatters;
use Consolidation\OutputFormatters\Validate\ValidDataTypesInterface;
use Consolidation\OutputFormatters\Options\FormatterOptions;
use Consolidation\OutputFormatters\Transformations\TableTransformation;
use Consolidation\OutputFormatters\Exception\IncompatibleDataException;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Tab-separated value formatters
*
* Display the provided structured data in a tab-separated list. Output
* escaping is much lighter, since there is no allowance for altering
* the delimiter.
*/
class TsvFormatter extends CsvFormatter
{
protected function getDefaultFormatterOptions()
{
return [
FormatterOptions::INCLUDE_FIELD_LABELS => false,
];
}
protected function writeOneLine(OutputInterface $output, $data, $options)
{
$output->writeln($this->tsvEscape($data));
}
protected function tsvEscape($data)
{
return implode("\t", array_map(
function ($item) {
return str_replace(["\t", "\n"], ['\t', '\n'], $item);
},
$data
));
}
}
<?php
namespace Consolidation\OutputFormatters\Formatters;
use Consolidation\OutputFormatters\Options\FormatterOptions;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Var_export formatter
*
* Run provided date thruogh var_export.
*/
class VarExportFormatter implements FormatterInterface
{
/**
* @inheritdoc
*/
public function write(OutputInterface $output, $data, FormatterOptions $options)
{
$output->writeln(var_export($data, true));
}
}
<?php
namespace Consolidation\OutputFormatters\Formatters;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Helper\TableStyle;
use Consolidation\OutputFormatters\Validate\ValidDataTypesInterface;
use Consolidation\OutputFormatters\Options\FormatterOptions;
use Consolidation\OutputFormatters\Validate\ValidDataTypesTrait;
use Consolidation\OutputFormatters\StructuredData\TableDataInterface;
use Consolidation\OutputFormatters\Transformations\ReorderFields;
use Consolidation\OutputFormatters\Exception\IncompatibleDataException;
use Consolidation\OutputFormatters\StructuredData\Xml\DomDataInterface;
/**
* Display a table of data with the Symfony Table class.
*
* This formatter takes data of either the RowsOfFields or
* PropertyList data type. Tables can be rendered with the
* rows running either vertically (the normal orientation) or
* horizontally. By default, associative lists will be displayed
* as two columns, with the key in the first column and the
* value in the second column.
*/
class XmlFormatter implements FormatterInterface, ValidDataTypesInterface
{
use ValidDataTypesTrait;
public function __construct()
{
}
public function validDataTypes()
{
return
[
new \ReflectionClass('\DOMDocument'),
new \ReflectionClass('\ArrayObject'),
];
}
/**
* @inheritdoc
*/
public function validate($structuredData)
{
if ($structuredData instanceof \DOMDocument) {
return $structuredData;
}
if ($structuredData instanceof DomDataInterface) {
return $structuredData->getDomData();
}
if (!is_array($structuredData)) {
throw new IncompatibleDataException(
$this,
$structuredData,
$this->validDataTypes()
);
}
return $structuredData;
}
/**
* @inheritdoc
*/
public function write(OutputInterface $output, $dom, FormatterOptions $options)
{
if (is_array($dom)) {
$schema = $options->getXmlSchema();
$dom = $schema->arrayToXML($dom);
}
$dom->formatOutput = true;
$output->writeln($dom->saveXML());
}
}
<?php
namespace Consolidation\OutputFormatters\Formatters;
use Symfony\Component\Yaml\Yaml;
use Consolidation\OutputFormatters\Options\FormatterOptions;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Yaml formatter
*
* Convert an array or ArrayObject into Yaml.
*/
class YamlFormatter implements FormatterInterface
{
/**
* @inheritdoc
*/
public function write(OutputInterface $output, $data, FormatterOptions $options)
{
// Set Yaml\Dumper's default indentation for nested nodes/collections to
// 2 spaces for consistency with Drupal coding standards.
$indent = 2;
// The level where you switch to inline YAML is set to PHP_INT_MAX to
// ensure this does not occur.
$output->writeln(Yaml::dump($data, PHP_INT_MAX, $indent, false, true));
}
}
<?php
namespace Consolidation\OutputFormatters\Options;
use Symfony\Component\Console\Input\InputInterface;
use Consolidation\OutputFormatters\Transformations\PropertyParser;
use Consolidation\OutputFormatters\StructuredData\Xml\XmlSchema;
use Consolidation\OutputFormatters\StructuredData\Xml\XmlSchemaInterface;
/**
* FormetterOptions holds information that affects the way a formatter
* renders its output.
*
* There are three places where a formatter might get options from:
*
* 1. Configuration associated with the command that produced the output.
* This is passed in to FormatterManager::write() along with the data
* to format. It might originally come from annotations on the command,
* or it might come from another source. Examples include the field labels
* for a table, or the default list of fields to display.
*
* 2. Options specified by the user, e.g. by commandline options.
*
* 3. Default values associated with the formatter itself.
*
* This class caches configuration from sources (1) and (2), and expects
* to be provided the defaults, (3), whenever a value is requested.
*/
class FormatterOptions
{
/** var array */
protected $configurationData = [];
/** var array */
protected $options = [];
/** var InputInterface */
protected $input;
const FORMAT = 'format';
const DEFAULT_FORMAT = 'default-format';
const TABLE_STYLE = 'table-style';
const LIST_ORIENTATION = 'list-orientation';
const FIELDS = 'fields';
const FIELD = 'field';
const INCLUDE_FIELD_LABELS = 'include-field-labels';
const ROW_LABELS = 'row-labels';
const FIELD_LABELS = 'field-labels';
const DEFAULT_FIELDS = 'default-fields';
const DEFAULT_STRING_FIELD = 'default-string-field';
const DELIMITER = 'delimiter';
/**
* Create a new FormatterOptions with the configuration data and the
* user-specified options for this request.
*
* @see FormatterOptions::setInput()
* @param array $configurationData
* @param array $options
*/
public function __construct($configurationData = [], $options = [])
{
$this->configurationData = $configurationData;
$this->options = $options;
}
/**
* Create a new FormatterOptions object with new configuration data (provided),
* and the same options data as this instance.
*
* @param array $configurationData
* @return FormatterOptions
*/
public function override($configurationData)
{
$override = new self();
$override
->setConfigurationData($configurationData + $this->getConfigurationData())
->setOptions($this->getOptions());
return $override;
}
public function setTableStyle($style)
{
return $this->setConfigurationValue(self::TABLE_STYLE, $style);
}
public function setIncludeFieldLables($includFieldLables)
{
return $this->setConfigurationValue(self::INCLUDE_FIELD_LABELS, $includFieldLables);
}
public function setListOrientation($listOrientation)
{
return $this->setConfigurationValue(self::LIST_ORIENTATION, $listOrientation);
}
public function setRowLabels($rowLabels)
{
return $this->setConfigurationValue(self::ROW_LABELS, $rowLabels);
}
public function setDefaultFields($fields)
{
return $this->setConfigurationValue(self::DEFAULT_FIELDS, $fields);
}
public function setFieldLabels($fieldLabels)
{
return $this->setConfigurationValue(self::FIELD_LABELS, $fieldLabels);
}
public function setDefaultStringField($defaultStringField)
{
return $this->setConfigurationValue(self::DEFAULT_STRING_FIELD, $defaultStringField);
}
/**
* Get a formatter option
*
* @param string $key
* @param array $defaults
* @param mixed $default
* @return mixed
*/
public function get($key, $defaults = [], $default = false)
{
$value = $this->fetch($key, $defaults, $default);
return $this->parse($key, $value);
}
/**
* Return the XmlSchema to use with --format=xml for data types that support
* that. This is used when an array needs to be converted into xml.
*
* @return XmlSchema
*/
public function getXmlSchema()
{
return new XmlSchema();
}
/**
* Determine the format that was requested by the caller.
*
* @param array $defaults
* @return string
*/
public function getFormat($defaults = [])
{
return $this->get(self::FORMAT, [], $this->get(self::DEFAULT_FORMAT, $defaults, ''));
}
/**
* Look up a key, and return its raw value.
*
* @param string $key
* @param array $defaults
* @param mixed $default
* @return mixed
*/
protected function fetch($key, $defaults = [], $default = false)
{
$defaults = $this->defaultsForKey($key, $defaults, $default);
$values = $this->fetchRawValues($defaults);
return $values[$key];
}
/**
* Reduce provided defaults to the single item identified by '$key',
* if it exists, or an empty array otherwise.
*
* @param string $key
* @param array $defaults
* @return array
*/
protected function defaultsForKey($key, $defaults, $default = false)
{
if (array_key_exists($key, $defaults)) {
return [$key => $defaults[$key]];
}
return [$key => $default];
}
/**
* Look up all of the items associated with the provided defaults.
*
* @param array $defaults
* @return array
*/
protected function fetchRawValues($defaults = [])
{
return array_merge(
$defaults,
$this->getConfigurationData(),
$this->getOptions(),
$this->getInputOptions($defaults)
);
}
/**
* Given the raw value for a specific key, do any type conversion
* (e.g. from a textual list to an array) needed for the data.
*
* @param string $key
* @param mixed $value
* @return mixed
*/
protected function parse($key, $value)
{
$optionFormat = $this->getOptionFormat($key);
if (!empty($optionFormat) && is_string($value)) {
return $this->$optionFormat($value);
}
return $value;
}
/**
* Convert from a textual list to an array
*
* @param string $value
* @return array
*/
public function parsePropertyList($value)
{
return PropertyParser::parse($value);
}
/**
* Given a specific key, return the class method name of the
* parsing method for data stored under this key.
*
* @param string $key
* @return string
*/
protected function getOptionFormat($key)
{
$propertyFormats = [
self::ROW_LABELS => 'PropertyList',
self::FIELD_LABELS => 'PropertyList',
];
if (array_key_exists($key, $propertyFormats)) {
return "parse{$propertyFormats[$key]}";
}
return '';
}
/**
* Change the configuration data for this formatter options object.
*
* @param array $configurationData
* @return FormatterOptions
*/
public function setConfigurationData($configurationData)
{
$this->configurationData = $configurationData;
return $this;
}
/**
* Change one configuration value for this formatter option.
*
* @param string $key
* @param mixed $value
* @return FormetterOptions
*/
protected function setConfigurationValue($key, $value)
{
$this->configurationData[$key] = $value;
return $this;
}
/**
* Change one configuration value for this formatter option, but only
* if it does not already have a value set.
*
* @param string $key
* @param mixed $value
* @return FormetterOptions
*/
public function setConfigurationDefault($key, $value)
{
if (!array_key_exists($key, $this->configurationData)) {
return $this->setConfigurationValue($key, $value);
}
return $this;
}
/**
* Return a reference to the configuration data for this object.
*
* @return array
*/
public function getConfigurationData()
{
return $this->configurationData;
}
/**
* Set all of the options that were specified by the user for this request.
*
* @param array $options
* @return FormatterOptions
*/
public function setOptions($options)
{
$this->options = $options;
return $this;
}
/**
* Change one option value specified by the user for this request.
*
* @param string $key
* @param mixed $value
* @return FormatterOptions
*/
public function setOption($key, $value)
{
$this->options[$key] = $value;
return $this;
}
/**
* Return a reference to the user-specified options for this request.
*
* @return array
*/
public function getOptions()
{
return $this->options;
}
/**
* Provide a Symfony Console InputInterface containing the user-specified
* options for this request.
*
* @param InputInterface $input
* @return type
*/
public function setInput(InputInterface $input)
{
$this->input = $input;
}
/**
* Return all of the options from the provided $defaults array that
* exist in our InputInterface object.
*
* @param array $defaults
* @return array
*/
public function getInputOptions($defaults)
{
if (!isset($this->input)) {
return [];
}
$options = [];
foreach ($defaults as $key => $value) {
if ($this->input->hasOption($key)) {
$result = $this->input->getOption($key);
if (isset($result)) {
$options[$key] = $this->input->getOption($key);
}
}
}
return $options;
}
}
<?php
namespace Consolidation\OutputFormatters\Options;
use Consolidation\OutputFormatters\Options\FormatterOptions;
interface OverrideOptionsInterface
{
/**
* Allow the formatter to mess with the configuration options before any
* transformations et. al. get underway.
*
* @param mixed $structuredOutput Data to restructure
* @param FormatterOptions $options Formatting options
* @return FormatterOptions
*/
public function overrideOptions($structuredOutput, FormatterOptions $options);
}
<?php
namespace Consolidation\OutputFormatters\StructuredData;
use Consolidation\OutputFormatters\StructuredData\RestructureInterface;
use Consolidation\OutputFormatters\Options\FormatterOptions;
use Consolidation\OutputFormatters\StructuredData\ListDataInterface;
use Consolidation\OutputFormatters\Transformations\ReorderFields;
use Consolidation\OutputFormatters\Transformations\TableTransformation;
/**
* Holds an array where each element of the array is one row,
* and each row contains an associative array where the keys
* are the field names, and the values are the field data.
*
* It is presumed that every row contains the same keys.
*/
abstract class AbstractStructuredList extends \ArrayObject implements RestructureInterface, ListDataInterface, RenderCellCollectionInterface
{
use RenderCellCollectionTrait;
protected $data;
public function __construct($data)
{
parent::__construct($data);
}
abstract public function restructure(FormatterOptions $options);
abstract public function getListData(FormatterOptions $options);
protected function createTableTransformation($data, $options)
{
$defaults = $this->defaultOptions();
$fieldLabels = $this->getReorderedFieldLabels($data, $options, $defaults);
$tableTransformer = $this->instantiateTableTransformation($data, $fieldLabels, $options->get(FormatterOptions::ROW_LABELS, $defaults));
if ($options->get(FormatterOptions::LIST_ORIENTATION, $defaults)) {
$tableTransformer->setLayout(TableTransformation::LIST_LAYOUT);
}
return $tableTransformer;
}
protected function instantiateTableTransformation($data, $fieldLabels, $rowLabels)
{
return new TableTransformation($data, $fieldLabels, $rowLabels);
}
protected function getReorderedFieldLabels($data, $options, $defaults)
{
$reorderer = new ReorderFields();
$fieldLabels = $reorderer->reorder(
$this->getFields($options, $defaults),
$options->get(FormatterOptions::FIELD_LABELS, $defaults),
$data
);
return $fieldLabels;
}
protected function getFields($options, $defaults)
{
$fieldShortcut = $options->get(FormatterOptions::FIELD);
if (!empty($fieldShortcut)) {
return [$fieldShortcut];
}
$result = $options->get(FormatterOptions::FIELDS, $defaults);
if (!empty($result)) {
return $result;
}
return $options->get(FormatterOptions::DEFAULT_FIELDS, $defaults);
}
/**
* A structured list may provide its own set of default options. These
* will be used in place of the command's default options (from the
* annotations) in instances where the user does not provide the options
* explicitly (on the commandline) or implicitly (via a configuration file).
*
* @return array
*/
protected function defaultOptions()
{
return [
FormatterOptions::FIELDS => [],
FormatterOptions::FIELD_LABELS => [],
FormatterOptions::ROW_LABELS => [],
FormatterOptions::DEFAULT_FIELDS => [],
];
}
}
<?php
namespace Consolidation\OutputFormatters\StructuredData;
/**
* Old name for PropertyList class.
*
* @deprecated
*/
class AssociativeList extends PropertyList
{
}
<?php
namespace Consolidation\OutputFormatters\StructuredData;
use Consolidation\OutputFormatters\Options\FormatterOptions;
class CallableRenderer implements RenderCellInterface
{
/** @var callable */
protected $renderFunction;
public function __construct(callable $renderFunction)
{
$this->renderFunction = $renderFunction;
}
/**
* {@inheritdoc}
*/
public function renderCell($key, $cellData, FormatterOptions $options, $rowData)
{
return call_user_func($this->renderFunction, $key, $cellData, $options, $rowData);
}
}
<?php
namespace Consolidation\OutputFormatters\StructuredData;
use Consolidation\OutputFormatters\Options\FormatterOptions;
interface ListDataInterface
{
/**
* Convert data to a format suitable for use in a list.
* By default, the array values will be used. Implement
* ListDataInterface to use some other criteria (e.g. array keys).
*
* @return array
*/
public function getListData(FormatterOptions $options);
}
<?php
namespace Consolidation\OutputFormatters\StructuredData;
interface OriginalDataInterface
{
/**
* Return the original data for this table. Used by any
* formatter that expects an array.
*/
public function getOriginalData();
}
<?php
namespace Consolidation\OutputFormatters\StructuredData;
use Consolidation\OutputFormatters\Options\FormatterOptions;
use Consolidation\OutputFormatters\StructuredData\ListDataInterface;
use Consolidation\OutputFormatters\Transformations\PropertyParser;
use Consolidation\OutputFormatters\Transformations\ReorderFields;
use Consolidation\OutputFormatters\Transformations\TableTransformation;
use Consolidation\OutputFormatters\Transformations\PropertyListTableTransformation;
/**
* Holds an array where each element of the array is one
* key : value pair. The keys must be unique, as is typically
* the case for associative arrays.
*/
class PropertyList extends AbstractStructuredList
{
/**
* Restructure this data for output by converting it into a table
* transformation object.
*
* @param FormatterOptions $options Options that affect output formatting.
* @return Consolidation\OutputFormatters\Transformations\TableTransformation
*/
public function restructure(FormatterOptions $options)
{
$data = [$this->getArrayCopy()];
$options->setConfigurationDefault('list-orientation', true);
$tableTransformer = $this->createTableTransformation($data, $options);
return $tableTransformer;
}
public function getListData(FormatterOptions $options)
{
$data = $this->getArrayCopy();
$defaults = $this->defaultOptions();
$fieldLabels = $this->getReorderedFieldLabels([$data], $options, $defaults);
$result = [];
foreach ($fieldLabels as $id => $label) {
$result[$id] = $data[$id];
}
return $result;
}
protected function defaultOptions()
{
return [
FormatterOptions::LIST_ORIENTATION => true,
] + parent::defaultOptions();
}
protected function instantiateTableTransformation($data, $fieldLabels, $rowLabels)
{
return new PropertyListTableTransformation($data, $fieldLabels, $rowLabels);
}
}
<?php
namespace Consolidation\OutputFormatters\StructuredData;
interface RenderCellCollectionInterface extends RenderCellInterface
{
const PRIORITY_FIRST = 'first';
const PRIORITY_NORMAL = 'normal';
const PRIORITY_FALLBACK = 'fallback';
/**
* Add a renderer
*
* @return $this
*/
public function addRenderer(RenderCellInterface $renderer);
}
<?php
namespace Consolidation\OutputFormatters\StructuredData;
use Consolidation\OutputFormatters\Options\FormatterOptions;
trait RenderCellCollectionTrait
{
/** @var RenderCellInterface[] */
protected $rendererList = [
RenderCellCollectionInterface::PRIORITY_FIRST => [],
RenderCellCollectionInterface::PRIORITY_NORMAL => [],
RenderCellCollectionInterface::PRIORITY_FALLBACK => [],
];
/**
* Add a renderer
*
* @return $this
*/
public function addRenderer(RenderCellInterface $renderer, $priority = RenderCellCollectionInterface::PRIORITY_NORMAL)
{
$this->rendererList[$priority][] = $renderer;
return $this;
}
/**
* Add a callable as a renderer
*
* @return $this
*/
public function addRendererFunction(callable $rendererFn, $priority = RenderCellCollectionInterface::PRIORITY_NORMAL)
{
$renderer = new CallableRenderer($rendererFn);
return $this->addRenderer($renderer, $priority);
}
/**
* {@inheritdoc}
*/
public function renderCell($key, $cellData, FormatterOptions $options, $rowData)
{
$flattenedRendererList = array_reduce(
$this->rendererList,
function ($carry, $item) {
return array_merge($carry, $item);
},
[]
);
foreach ($flattenedRendererList as $renderer) {
$cellData = $renderer->renderCell($key, $cellData, $options, $rowData);
if (is_string($cellData)) {
return $cellData;
}
}
return $cellData;
}
}
<?php
namespace Consolidation\OutputFormatters\StructuredData;
use Consolidation\OutputFormatters\Options\FormatterOptions;
interface RenderCellInterface
{
/**
* Convert the contents of one table cell into a string,
* so that it may be placed in the table. Renderer should
* return the $cellData passed to it if it does not wish to
* process it.
*
* @param string $key Identifier of the cell being rendered
* @param mixed $cellData The data to render
* @param FormatterOptions $options The formatting options
* @param array $rowData The rest of the row data
*
* @return mixed
*/
public function renderCell($key, $cellData, FormatterOptions $options, $rowData);
}
<?php
namespace Consolidation\OutputFormatters\StructuredData;
use Consolidation\OutputFormatters\Options\FormatterOptions;
interface RestructureInterface
{
/**
* Allow structured data to be restructured -- i.e. to select fields
* to show, reorder fields, etc.
*
* @param FormatterOptions $options Formatting options
*/
public function restructure(FormatterOptions $options);
}
<?php
namespace Consolidation\OutputFormatters\StructuredData;
use Consolidation\OutputFormatters\Options\FormatterOptions;
/**
* Holds an array where each element of the array is one row,
* and each row contains an associative array where the keys
* are the field names, and the values are the field data.
*
* It is presumed that every row contains the same keys.
*/
class RowsOfFields extends AbstractStructuredList
{
/**
* Restructure this data for output by converting it into a table
* transformation object.
*
* @param FormatterOptions $options Options that affect output formatting.
* @return Consolidation\OutputFormatters\Transformations\TableTransformation
*/
public function restructure(FormatterOptions $options)
{
$data = $this->getArrayCopy();
return $this->createTableTransformation($data, $options);
}
public function getListData(FormatterOptions $options)
{
return array_keys($this->getArrayCopy());
}
protected function defaultOptions()
{
return [
FormatterOptions::LIST_ORIENTATION => false,
] + parent::defaultOptions();
}
}
<?php
namespace Consolidation\OutputFormatters\StructuredData;
interface TableDataInterface
{
/**
* Return the original data for this table. Used by any
* formatter that is -not- a table.
*/
public function getOriginalData();
/**
* Convert structured data into a form suitable for use
* by the table formatter.
*
* @param boolean $includeRowKey Add a field containing the
* key from each row.
*
* @return array
*/
public function getTableData($includeRowKey = false);
}
<?php
namespace Consolidation\OutputFormatters\StructuredData\Xml;
interface DomDataInterface
{
/**
* Convert data into a \DomDocument.
*
* @return \DomDocument
*/
public function getDomData();
}
<?php
namespace Consolidation\OutputFormatters\StructuredData\Xml;
class XmlSchema implements XmlSchemaInterface
{
protected $elementList;
public function __construct($elementList = [])
{
$defaultElementList =
[
'*' => ['description'],
];
$this->elementList = array_merge_recursive($elementList, $defaultElementList);
}
public function arrayToXML($structuredData)
{
$dom = new \DOMDocument('1.0', 'UTF-8');
$topLevelElement = $this->getTopLevelElementName($structuredData);
$this->addXmlData($dom, $dom, $topLevelElement, $structuredData);
return $dom;
}
protected function addXmlData(\DOMDocument $dom, $xmlParent, $elementName, $structuredData)
{
$element = $dom->createElement($elementName);
$xmlParent->appendChild($element);
if (is_string($structuredData)) {
$element->appendChild($dom->createTextNode($structuredData));
return;
}
$this->addXmlChildren($dom, $element, $elementName, $structuredData);
}
protected function addXmlChildren(\DOMDocument $dom, $xmlParent, $elementName, $structuredData)
{
foreach ($structuredData as $key => $value) {
$this->addXmlDataOrAttribute($dom, $xmlParent, $elementName, $key, $value);
}
}
protected function addXmlDataOrAttribute(\DOMDocument $dom, $xmlParent, $elementName, $key, $value)
{
$childElementName = $this->getDefaultElementName($elementName);
$elementName = is_numeric($key) ? $childElementName : $key;
if (($elementName != $childElementName) && $this->isAttribute($elementName, $key, $value)) {
$xmlParent->setAttribute($key, $value);
return;
}
$this->addXmlData($dom, $xmlParent, $elementName, $value);
}
protected function getTopLevelElementName($structuredData)
{
return 'document';
}
protected function getDefaultElementName($parentElementName)
{
$singularName = $this->singularForm($parentElementName);
if (isset($singularName)) {
return $singularName;
}
return 'item';
}
protected function isAttribute($parentElementName, $elementName, $value)
{
if (!is_string($value)) {
return false;
}
return !$this->inElementList($parentElementName, $elementName) && !$this->inElementList('*', $elementName);
}
protected function inElementList($parentElementName, $elementName)
{
if (!array_key_exists($parentElementName, $this->elementList)) {
return false;
}
return in_array($elementName, $this->elementList[$parentElementName]);
}
protected function singularForm($name)
{
if (substr($name, strlen($name) - 1) == "s") {
return substr($name, 0, strlen($name) - 1);
}
}
protected function isAssoc($data)
{
return array_keys($data) == range(0, count($data));
}
}
<?php
namespace Consolidation\OutputFormatters\StructuredData\Xml;
/**
* When using arrays, we could represent XML data in a number of
* different ways.
*
* For example, given the following XML data strucutre:
*
* <document id="1" name="doc">
* <foobars>
* <foobar id="123">
* <name>blah</name>
* <widgets>
* <widget>
* <foo>a</foo>
* <bar>b</bar>
* <baz>c</baz>
* </widget>
* </widgets>
* </foobar>
* </foobars>
* </document>
*
* This could be:
*
* [
* 'id' => 1,
* 'name' => 'doc',
* 'foobars' =>
* [
* [
* 'id' => '123',
* 'name' => 'blah',
* 'widgets' =>
* [
* [
* 'foo' => 'a',
* 'bar' => 'b',
* 'baz' => 'c',
* ]
* ],
* ],
* ]
* ]
*
* The challenge is more in going from an array back to the more
* structured xml format. Note that any given key => string mapping
* could represent either an attribute, or a simple XML element
* containing only a string value. In general, we do *not* want to add
* extra layers of nesting in the data structure to disambiguate between
* these kinds of data, as we want the source data to render cleanly
* into other formats, e.g. yaml, json, et. al., and we do not want to
* force every data provider to have to consider the optimal xml schema
* for their data.
*
* Our strategy, therefore, is to expect clients that wish to provide
* a very specific xml representation to return a DOMDocument, and,
* for other data structures where xml is a secondary concern, then we
* will use some default heuristics to convert from arrays to xml.
*/
interface XmlSchemaInterface
{
/**
* Convert data to a format suitable for use in a list.
* By default, the array values will be used. Implement
* ListDataInterface to use some other criteria (e.g. array keys).
*
* @return \DOMDocument
*/
public function arrayToXml($structuredData);
}
<?php
namespace Consolidation\OutputFormatters\Transformations;
use Consolidation\OutputFormatters\Options\FormatterOptions;
use Consolidation\OutputFormatters\StructuredData\Xml\DomDataInterface;
use Consolidation\OutputFormatters\StructuredData\Xml\XmlSchema;
/**
* Simplify a DOMDocument to an array.
*/
class DomToArraySimplifier implements SimplifyToArrayInterface
{
public function __construct()
{
}
/**
* @param ReflectionClass $dataType
*/
public function canSimplify(\ReflectionClass $dataType)
{
return
$dataType->isSubclassOf('\Consolidation\OutputFormatters\StructuredData\Xml\DomDataInterface') ||
$dataType->isSubclassOf('DOMDocument') ||
($dataType->getName() == 'DOMDocument');
}
public function simplifyToArray($structuredData, FormatterOptions $options)
{
if ($structuredData instanceof DomDataInterface) {
$structuredData = $structuredData->getDomData();
}
if ($structuredData instanceof \DOMDocument) {
// $schema = $options->getXmlSchema();
$simplified = $this->elementToArray($structuredData);
$structuredData = array_shift($simplified);
}
return $structuredData;
}
/**
* Recursively convert the provided DOM element into a php array.
*
* @param \DOMNode $element
* @return array
*/
protected function elementToArray(\DOMNode $element)
{
if ($element->nodeType == XML_TEXT_NODE) {
return $element->nodeValue;
}
$attributes = $this->getNodeAttributes($element);
$children = $this->getNodeChildren($element);
return array_merge($attributes, $children);
}
/**
* Get all of the attributes of the provided element.
*
* @param \DOMNode $element
* @return array
*/
protected function getNodeAttributes($element)
{
if (empty($element->attributes)) {
return [];
}
$attributes = [];
foreach ($element->attributes as $key => $attribute) {
$attributes[$key] = $attribute->nodeValue;
}
return $attributes;
}
/**
* Get all of the children of the provided element, with simplification.
*
* @param \DOMNode $element
* @return array
*/
protected function getNodeChildren($element)
{
if (empty($element->childNodes)) {
return [];
}
$uniformChildrenName = $this->hasUniformChildren($element);
if ("{$uniformChildrenName}s" == $element->nodeName) {
$result = $this->getUniformChildren($element->nodeName, $element);
} else {
$result = $this->getUniqueChildren($element->nodeName, $element);
}
return $result;
}
/**
* Get the data from the children of the provided node in preliminary
* form.
*
* @param \DOMNode $element
* @return array
*/
protected function getNodeChildrenData($element)
{
$children = [];
foreach ($element->childNodes as $key => $value) {
$children[$key] = $this->elementToArray($value);
}
return $children;
}
/**
* Determine whether the children of the provided element are uniform.
* @see getUniformChildren(), below.
*
* @param \DOMNode $element
* @return boolean
*/
protected function hasUniformChildren($element)
{
$last = false;
foreach ($element->childNodes as $key => $value) {
$name = $value->nodeName;
if (!$name) {
return false;
}
if ($last && ($name != $last)) {
return false;
}
$last = $name;
}
return $last;
}
/**
* Convert the children of the provided DOM element into an array.
* Here, 'uniform' means that all of the element names of the children
* are identical, and further, the element name of the parent is the
* plural form of the child names. When the children are uniform in
* this way, then the parent element name will be used as the key to
* store the children in, and the child list will be returned as a
* simple list with their (duplicate) element names omitted.
*
* @param string $parentKey
* @param \DOMNode $element
* @return array
*/
protected function getUniformChildren($parentKey, $element)
{
$children = $this->getNodeChildrenData($element);
$simplifiedChildren = [];
foreach ($children as $key => $value) {
if ($this->valueCanBeSimplified($value)) {
$value = array_shift($value);
}
$simplifiedChildren[$parentKey][] = $value;
}
return $simplifiedChildren;
}
/**
* Determine whether the provided value has additional unnecessary
* nesting. {"color": "red"} is converted to "red". No other
* simplification is done.
*
* @param \DOMNode $value
* @return boolean
*/
protected function valueCanBeSimplified($value)
{
if (!is_array($value)) {
return false;
}
if (count($value) != 1) {
return false;
}
$data = array_shift($value);
return is_string($data);
}
/**
* Convert the children of the provided DOM element into an array.
* Here, 'unique' means that all of the element names of the children are
* different. Since the element names will become the key of the
* associative array that is returned, so duplicates are not supported.
* If there are any duplicates, then an exception will be thrown.
*
* @param string $parentKey
* @param \DOMNode $element
* @return array
*/
protected function getUniqueChildren($parentKey, $element)
{
$children = $this->getNodeChildrenData($element);
if ((count($children) == 1) && (is_string($children[0]))) {
return [$element->nodeName => $children[0]];
}
$simplifiedChildren = [];
foreach ($children as $key => $value) {
if (is_numeric($key) && is_array($value) && (count($value) == 1)) {
$valueKeys = array_keys($value);
$key = $valueKeys[0];
$value = array_shift($value);
}
if (array_key_exists($key, $simplifiedChildren)) {
throw new \Exception("Cannot convert data from a DOM document to an array, because <$key> appears more than once, and is not wrapped in a <{$key}s> element.");
}
$simplifiedChildren[$key] = $value;
}
return $simplifiedChildren;
}
}
<?php
namespace Consolidation\OutputFormatters\Transformations;
use Consolidation\OutputFormatters\Options\FormatterOptions;
interface OverrideRestructureInterface
{
/**
* Select data to use directly from the structured output,
* before the restructure operation has been executed.
*
* @param mixed $structuredOutput Data to restructure
* @param FormatterOptions $options Formatting options
* @return mixed
*/
public function overrideRestructure($structuredOutput, FormatterOptions $options);
}
<?php
namespace Consolidation\OutputFormatters\Transformations;
class PropertyListTableTransformation extends TableTransformation
{
public function getOriginalData()
{
$data = $this->getArrayCopy();
return $data[0];
}
}
<?php
namespace Consolidation\OutputFormatters\Transformations;
/**
* Transform a string of properties into a PHP associative array.
*
* Input:
*
* one: red
* two: white
* three: blue
*
* Output:
*
* [
* 'one' => 'red',
* 'two' => 'white',
* 'three' => 'blue',
* ]
*/
class PropertyParser
{
public static function parse($data)
{
if (!is_string($data)) {
return $data;
}
$result = [];
$lines = explode("\n", $data);
foreach ($lines as $line) {
list($key, $value) = explode(':', trim($line), 2) + ['', ''];
$result[$key] = trim($value);
}
return $result;
}
}
<?php
namespace Consolidation\OutputFormatters\Transformations;
use Symfony\Component\Finder\Glob;
use Consolidation\OutputFormatters\Exception\UnknownFieldException;
/**
* Reorder the field labels based on the user-selected fields
* to display.
*/
class ReorderFields
{
/**
* Given a simple list of user-supplied field keys or field labels,
* return a reordered version of the field labels matching the
* user selection.
*
* @param string|array $fields The user-selected fields
* @param array $fieldLabels An associative array mapping the field
* key to the field label
* @param array $data The data that will be rendered.
*
* @return array
*/
public function reorder($fields, $fieldLabels, $data)
{
$firstRow = reset($data);
if (!$firstRow) {
$firstRow = $fieldLabels;
}
if (empty($fieldLabels) && !empty($data)) {
$fieldLabels = array_combine(array_keys($firstRow), array_map('ucfirst', array_keys($firstRow)));
}
$fields = $this->getSelectedFieldKeys($fields, $fieldLabels);
if (empty($fields)) {
return array_intersect_key($fieldLabels, $firstRow);
}
return $this->reorderFieldLabels($fields, $fieldLabels, $data);
}
protected function reorderFieldLabels($fields, $fieldLabels, $data)
{
$result = [];
$firstRow = reset($data);
foreach ($fields as $field) {
if (array_key_exists($field, $firstRow)) {
if (array_key_exists($field, $fieldLabels)) {
$result[$field] = $fieldLabels[$field];
}
}
}
return $result;
}
protected function getSelectedFieldKeys($fields, $fieldLabels)
{
if (is_string($fields)) {
$fields = explode(',', $fields);
}
$selectedFields = [];
foreach ($fields as $field) {
$matchedFields = $this->matchFieldInLabelMap($field, $fieldLabels);
if (empty($matchedFields)) {
throw new UnknownFieldException($field);
}
$selectedFields = array_merge($selectedFields, $matchedFields);
}
return $selectedFields;
}
protected function matchFieldInLabelMap($field, $fieldLabels)
{
$fieldRegex = $this->convertToRegex($field);
return
array_filter(
array_keys($fieldLabels),
function ($key) use ($fieldRegex, $fieldLabels) {
$value = $fieldLabels[$key];
return preg_match($fieldRegex, $value) || preg_match($fieldRegex, $key);
}
);
}
/**
* Convert the provided string into a regex suitable for use in
* preg_match.
*
* Matching occurs in the same way as the Symfony Finder component:
* http://symfony.com/doc/current/components/finder.html#file-name
*/
protected function convertToRegex($str)
{
return $this->isRegex($str) ? $str : Glob::toRegex($str);
}
/**
* Checks whether the string is a regex. This function is copied from
* MultiplePcreFilterIterator in the Symfony Finder component.
*
* @param string $str
*
* @return bool Whether the given string is a regex
*/
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 (array(array('{', '}'), array('(', ')'), array('[', ']'), array('<', '>')) as $delimiters) {
if ($start === $delimiters[0] && $end === $delimiters[1]) {
return true;
}
}
}
return false;
}
}
<?php
namespace Consolidation\OutputFormatters\Transformations;
use Consolidation\OutputFormatters\Options\FormatterOptions;
interface SimplifyToArrayInterface
{
/**
* Convert structured data into a generic array, usable by generic
* array-based formatters. Objects that implement this interface may
* be attached to the FormatterManager, and will be used on any data
* structure that needs to be simplified into an array. An array
* simplifier should take no action other than to return its input data
* if it cannot simplify the provided data into an array.
*
* @param mixed $structuredOutput The data to simplify to an array.
*
* @return array
*/
public function simplifyToArray($structuredOutput, FormatterOptions $options);
/**
* Indicate whether or not the given data type can be simplified to an array
*/
public function canSimplify(\ReflectionClass $structuredOutput);
}
<?php
namespace Consolidation\OutputFormatters\Transformations;
use Consolidation\OutputFormatters\StructuredData\TableDataInterface;
use Consolidation\OutputFormatters\StructuredData\OriginalDataInterface;
class TableTransformation extends \ArrayObject implements TableDataInterface, OriginalDataInterface
{
protected $headers;
protected $rowLabels;
protected $layout;
const TABLE_LAYOUT = 'table';
const LIST_LAYOUT = 'list';
public function __construct($data, $fieldLabels, $rowLabels = [])
{
$this->headers = $fieldLabels;
$this->rowLabels = $rowLabels;
$rows = static::transformRows($data, $fieldLabels);
$this->layout = self::TABLE_LAYOUT;
parent::__construct($rows);
}
public function setLayout($layout)
{
$this->layout = $layout;
}
public function getLayout()
{
return $this->layout;
}
public function isList()
{
return $this->layout == self::LIST_LAYOUT;
}
protected static function transformRows($data, $fieldLabels)
{
$rows = [];
foreach ($data as $rowid => $row) {
$rows[$rowid] = static::transformRow($row, $fieldLabels);
}
return $rows;
}
protected static function transformRow($row, $fieldLabels)
{
$result = [];
foreach ($fieldLabels as $key => $label) {
$result[$key] = array_key_exists($key, $row) ? $row[$key] : '';
}
return $result;
}
public function getHeaders()
{
return $this->headers;
}
public function getHeader($key)
{
if (array_key_exists($key, $this->headers)) {
return $this->headers[$key];
}
return $key;
}
public function getRowLabels()
{
return $this->rowLabels;
}
public function getRowLabel($rowid)
{
if (array_key_exists($rowid, $this->rowLabels)) {
return $this->rowLabels[$rowid];
}
return $rowid;
}
public function getOriginalData()
{
return $this->getArrayCopy();
}
public function getTableData($includeRowKey = false)
{
$data = $this->getArrayCopy();
if ($this->isList()) {
$data = $this->convertTableToList();
}
if ($includeRowKey) {
$data = $this->getRowDataWithKey($data);
}
return $data;
}
protected function convertTableToList()
{
$result = [];
foreach ($this as $row) {
foreach ($row as $key => $value) {
$result[$key][] = $value;
}
}
return $result;
}
protected function getRowDataWithKey($data)
{
$result = [];
$i = 0;
foreach ($data as $key => $row) {
array_unshift($row, $this->getHeader($key));
$i++;
$result[$key] = $row;
}
return $result;
}
}
<?php
namespace Consolidation\OutputFormatters\Validate;
/**
* Formatters may implement ValidationInterface in order to indicate
* whether a particular data structure is supported. Any formatter that does
* not implement ValidationInterface is assumed to only operate on arrays,
* or data types that implement SimplifyToArrayInterface.
*/
interface ValidationInterface
{
/**
* Return true if the specified format is valid for use with
* this formatter.
*/
public function isValidDataType(\ReflectionClass $dataType);
/**
* Throw an IncompatibleDataException if the provided data cannot
* be processed by this formatter. Return the source data if it
* is valid. The data may be encapsulated or converted if necessary.
*
* @param mixed $structuredData Data to validate
*
* @return mixed
*/
public function validate($structuredData);
}
<?php
namespace Consolidation\OutputFormatters\Validate;
/**
* Formatters may implement ValidDataTypesInterface in order to indicate
* exactly which formats they support. The validDataTypes method can be
* called to retrieve a list of data types useful in providing hints in
* exception messages about which data types can be used with the formatter.
*
* Note that it is OPTIONAL for formatters to implement this interface.
* If a formatter implements only ValidationInterface, then clients that
* request the formatter via FormatterManager::write() will still get a list
* (via an InvalidFormatException) of all of the formats that are usable
* with the provided data type. Implementing ValidDataTypesInterface is
* benefitial to clients who instantiate a formatter directly (via `new`).
*
* Formatters that implement ValidDataTypesInterface may wish to use
* ValidDataTypesTrait.
*/
interface ValidDataTypesInterface extends ValidationInterface
{
/**
* Return the list of data types acceptable to this formatter
*
* @return \ReflectionClass[]
*/
public function validDataTypes();
}
<?php
namespace Consolidation\OutputFormatters\Validate;
/**
* Provides a default implementation of isValidDataType.
*
* Users of this trait are expected to implement ValidDataTypesInterface.
*/
trait ValidDataTypesTrait
{
/**
* Return the list of data types acceptable to this formatter
*
* @return \ReflectionClass[]
*/
public abstract function validDataTypes();
/**
* Return the list of data types acceptable to this formatter
*/
public function isValidDataType(\ReflectionClass $dataType)
{
return array_reduce(
$this->validDataTypes(),
function ($carry, $supportedType) use ($dataType) {
return
$carry ||
($dataType->getName() == $supportedType->getName()) ||
($dataType->isSubclassOf($supportedType->getName()));
},
false
);
}
}
<?php
/**
* @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
*/
namespace Interop\Container;
use Interop\Container\Exception\ContainerException;
use Interop\Container\Exception\NotFoundException;
/**
* Describes the interface of a container that exposes methods to read its entries.
*/
interface ContainerInterface
{
/**
* Finds an entry of the container by its identifier and returns it.
*
* @param string $id Identifier of the entry to look for.
*
* @throws NotFoundException No entry was found for this identifier.
* @throws ContainerException Error while retrieving the entry.
*
* @return mixed Entry.
*/
public function get($id);
/**
* Returns true if the container can return an entry for the given identifier.
* Returns false otherwise.
*
* @param string $id Identifier of the entry to look for.
*
* @return boolean
*/
public function has($id);
}
<?php
/**
* @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
*/
namespace Interop\Container\Exception;
/**
* Base interface representing a generic exception in a container.
*/
interface ContainerException
{
}
<?php
/**
* @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
*/
namespace Interop\Container\Exception;
/**
* No entry was found in the container.
*/
interface NotFoundException extends ContainerException
{
}
<?php
namespace Lurker\Event;
use Lurker\Resource\TrackedResource;
use Lurker\Exception\InvalidArgumentException;
use Lurker\Resource\ResourceInterface;
use Lurker\Resource\FileResource;
use Lurker\Resource\DirectoryResource;
use Symfony\Component\EventDispatcher\Event;
/**
* Resource change event.
*
* @author Konstantin Kudryashov <ever.zet@gmail.com>
*/
class FilesystemEvent extends Event
{
const CREATE = 1;
const MODIFY = 2;
const DELETE = 4;
const ALL = 7;
private $tracked;
private $resource;
private $type;
protected static $types = array(
1 => 'create',
2 => 'modify',
4 => 'delete',
);
/**
* Initializes resource event.
*
* @param TrackedResource $tracked resource, that being tracked
* @param ResourceInterface $resource resource instance
* @param integer $type event type bit
*/
public function __construct(TrackedResource $tracked, ResourceInterface $resource, $type)
{
if (!isset(self::$types[$type])) {
throw new InvalidArgumentException('Wrong event type providen');
}
$this->tracked = $tracked;
$this->resource = $resource;
$this->type = $type;
}
/**
* Returns resource, that being tracked while event occured.
*
* @return integer
*/
public function getTrackedResource()
{
return $this->tracked;
}
/**
* Returns changed resource.
*
* @return ResourceInterface
*/
public function getResource()
{
return $this->resource;
}
/**
* Returns true is resource, that fired event is file.
*
* @return Boolean
*/
public function isFileChange()
{
return $this->resource instanceof FileResource;
}
/**
* Returns true is resource, that fired event is directory.
*
* @return Boolean
*/
public function isDirectoryChange()
{
return $this->resource instanceof DirectoryResource;
}
/**
* Returns event type.
*
* @return integer
*/
public function getType()
{
return $this->type;
}
/**
* Returns event type string representation.
*
* @return string
*/
public function getTypeString()
{
return self::$types[$this->getType()];
}
}
<?php
namespace Lurker\Exception;
/**
* Exception interface for all exceptions thrown by the component.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
interface ExceptionInterface
{
}
<?php
namespace Lurker\Exception;
use \InvalidArgumentException as BaseInvalidArgumentException;
/**
* InvalidArgumentException
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class InvalidArgumentException extends BaseInvalidArgumentException implements ExceptionInterface
{
}
<?php
namespace Lurker\Exception;
use \RuntimeException as BaseRuntimeException;
/**
* RuntimeException
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class RuntimeException extends BaseRuntimeException implements ExceptionInterface
{
}
<?php
namespace Lurker\Resource;
use Symfony\Component\Config\Resource\DirectoryResource as BaseDirectoryResource;
/**
* @package Lurker
*/
class DirectoryResource extends BaseDirectoryResource implements ResourceInterface
{
public function exists()
{
clearstatcache(true, $resource = $this->getResource());
return is_dir($resource);
}
public function getModificationTime()
{
if (!$this->exists()) {
return -1;
}
clearstatcache(true, $this->getResource());
if (false === $mtime = @filemtime($this->getResource())) {
return -1;
}
return $mtime;
}
public function isFresh($timestamp)
{
if (!$this->exists()) {
return false;
}
return $this->getModificationTime() < $timestamp;
}
public function getId()
{
return md5('d' . $this . $this->getPattern());
}
public function hasFile($file)
{
if (!$file instanceof \SplFileInfo) {
$file = new \SplFileInfo($file);
}
if (0 !== strpos($file->getRealPath(), realpath($this->getResource()))) {
return false;
}
if ($this->getPattern()) {
return (bool) preg_match($this->getPattern(), $file->getBasename());
}
return true;
}
public function getFilteredResources()
{
if (!$this->exists()) {
return array();
}
// race conditions
try {
$iterator = new \DirectoryIterator($this->getResource());
} catch (\UnexpectedValueException $e) {
return array();
}
$resources = array();
foreach ($iterator as $file) {
// if regex filtering is enabled only return matching files
if ($file->isFile() && !$this->hasFile($file)) {
continue;
}
// always monitor directories for changes, except the .. entries
// (otherwise deleted files wouldn't get detected)
if ($file->isDir() && '/..' === substr($file, -3)) {
continue;
}
// if file is dot - continue
if ($file->isDot()) {
continue;
}
if ($file->isFile()) {
$resources[] = new FileResource($file->getRealPath());
} elseif ($file->isDir()) {
$resources[] = new DirectoryResource($file->getRealPath());
}
}
return $resources;
}
}
<?php
namespace Lurker\Resource;
use Symfony\Component\Config\Resource\FileResource as BaseFileResource;
/**
* @package Lurker
*/
class FileResource extends BaseFileResource implements ResourceInterface
{
public function getModificationTime()
{
if (!$this->exists()) {
return -1;
}
clearstatcache(true, $this->getResource());
if (false === $mtime = @filemtime($this->getResource())) {
return -1;
}
return $mtime;
}
public function getId()
{
return md5('f' . $this);
}
public function isFresh($timestamp)
{
if (!$this->exists()) {
return false;
}
return $this->getModificationTime() < $timestamp;
}
public function exists()
{
clearstatcache(true, $this->getResource());
return is_file($this);
}
}
<?php
namespace Lurker\Resource;
use Symfony\Component\Config\Resource\ResourceInterface as BaseResourceInterface;
/**
* @package Lurker
*/
interface ResourceInterface extends BaseResourceInterface
{
/**
* @return boolean
*/
public function exists();
/**
* @return integer
*/
public function getModificationTime();
/**
* @return string
*/
public function getId();
}
<?php
namespace Lurker\Resource;
use Lurker\Exception\InvalidArgumentException;
/**
* Wraps usual resource with tracker information.
*
* @author Konstantin Kudryashov <ever.zet@gmail.com>
*/
class TrackedResource
{
private $trackingId;
private $resource;
/**
* Initializes tracked resource.
*
* @param string $trackingId id of the tracked resource
* @param ResourceInterface $resource resource
*/
public function __construct($trackingId, ResourceInterface $resource)
{
if (!$resource->exists()) {
throw new InvalidArgumentException(sprintf(
'Unable to track a non-existent resource (%s)', $resource
));
}
$this->trackingId = $trackingId;
$this->resource = $resource;
}
/**
* Returns tracking ID of the resource.
*
* @return string
*/
public function getTrackingId()
{
return $this->trackingId;
}
/**
* Returns original resource instance.
*
* @return ResourceInterface
*/
public function getOriginalResource()
{
return $this->resource;
}
}
<?php
namespace Lurker;
use Lurker\Event\FilesystemEvent;
use Lurker\Exception\InvalidArgumentException;
use Lurker\Resource\DirectoryResource;
use Lurker\Resource\FileResource;
use Lurker\Resource\ResourceInterface;
use Lurker\Resource\TrackedResource;
use Lurker\Tracker\InotifyTracker;
use Lurker\Tracker\RecursiveIteratorTracker;
use Lurker\Tracker\TrackerInterface;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
/**
* Resource changes watcher.
*
* @author Konstantin Kudryashov <ever.zet@gmail.com>
*/
class ResourceWatcher
{
private $tracker;
private $eventDispatcher;
private $watching = false;
/**
* Initializes path watcher.
*
* @param TrackerInterface $tracker
* @param EventDispatcherInterface $eventDispatcher
*/
public function __construct(TrackerInterface $tracker = null, EventDispatcherInterface $eventDispatcher = null)
{
if (null === $tracker) {
if (function_exists('inotify_init')) {
$tracker = new InotifyTracker();
} else {
$tracker = new RecursiveIteratorTracker();
}
}
if (null === $eventDispatcher) {
$eventDispatcher = new EventDispatcher();
}
$this->tracker = $tracker;
$this->eventDispatcher = $eventDispatcher;
}
/**
* Returns current tracker instance.
*
* @return TrackerInterface
*/
public function getTracker()
{
return $this->tracker;
}
/**
* Returns event dispatcher mapped to this tracker.
*
* @return EventDispatcherInterface
*/
public function getEventDispatcher()
{
return $this->eventDispatcher;
}
/**
* Track resource with watcher.
*
* @param string $trackingId id to this track (used for events naming)
* @param ResourceInterface|string $resource resource to track
* @param integer $eventsMask event types bitmask
*
* @throws InvalidArgumentException If 'all' is used as a tracking id
*/
public function track($trackingId, $resource, $eventsMask = FilesystemEvent::ALL)
{
if ('all' === $trackingId) {
throw new InvalidArgumentException(
'"all" is a reserved keyword and can not be used as tracking id'
);
}
if (!$resource instanceof ResourceInterface) {
if (is_file($resource)) {
$resource = new FileResource($resource);
} elseif (is_dir($resource)) {
$resource = new DirectoryResource($resource);
} else {
throw new InvalidArgumentException(sprintf(
'Second argument to track() should be either file or directory resource, '.
'but got "%s"',
$resource
));
}
}
$trackedResource = new TrackedResource($trackingId, $resource);
$this->getTracker()->track($trackedResource, $eventsMask);
}
/**
* Adds callback as specific tracking listener.
*
* @param string $trackingId id to this track (used for events naming)
* @param callable $callback callback to call on change
*
* @throws InvalidArgumentException If $callback argument isn't callable
*/
public function addListener($trackingId, $callback)
{
if (!is_callable($callback)) {
throw new InvalidArgumentException(sprintf(
'Second argument to listen() should be callable, but got %s', gettype($callback)
));
}
$this->getEventDispatcher()->addListener('resource_watcher.'.$trackingId, $callback);
}
/**
* Tracks specific resource change by provided callback.
*
* @param ResourceInterface|string $resource resource to track
* @param callable $callback callback to call on change
* @param integer $eventsMask event types bitmask
*/
public function trackByListener($resource, $callback, $eventsMask = FilesystemEvent::ALL)
{
$this->track($trackingId = md5((string) $resource.$eventsMask), $resource, $eventsMask);
$this->addListener($trackingId, $callback);
}
/**
* Returns true if watcher is currently watching on tracked resources (started).
*
* @return Boolean
*/
public function isWatching()
{
return $this->watching;
}
/**
* Starts watching on tracked resources.
*
* @param integer $checkInterval check interval in microseconds
* @param integer $timeLimit maximum watching time limit in microseconds
*/
public function start($checkInterval = 1000000, $timeLimit = null)
{
$totalTime = 0;
$this->watching = true;
while ($this->watching) {
usleep($checkInterval);
$totalTime += $checkInterval;
if (null !== $timeLimit && $totalTime > $timeLimit) {
break;
}
foreach ($this->getTracker()->getEvents() as $event) {
$trackedResource = $event->getTrackedResource();
// fire global event
$this->getEventDispatcher()->dispatch(
'resource_watcher.all',
$event
);
// fire specific trackingId event
$this->getEventDispatcher()->dispatch(
sprintf('resource_watcher.%s', $trackedResource->getTrackingId()),
$event
);
}
}
$this->watching = false;
}
/**
* Stop watching on tracked resources.
*/
public function stop()
{
$this->watching = false;
}
}
<?php
namespace Lurker\StateChecker;
use Lurker\Event\FilesystemEvent;
use Lurker\Resource\DirectoryResource;
/**
* Recursive directory state checker.
*
* @author Konstantin Kudryashov <ever.zet@gmail.com>
*/
class DirectoryStateChecker extends NewDirectoryStateChecker
{
/**
* {@inheritdoc}
*/
public function __construct(DirectoryResource $resource, $eventsMask = FilesystemEvent::ALL)
{
parent::__construct($resource, $eventsMask);
foreach ($this->createDirectoryChildCheckers($resource) as $checker) {
$this->childs[$checker->getResource()->getId()] = $checker;
}
}
}
<?php
namespace Lurker\StateChecker;
use Lurker\Resource\FileResource;
use Lurker\Event\FilesystemEvent;
/**
* File state checker.
*
* @author Konstantin Kudryashov <ever.zet@gmail.com>
*/
class FileStateChecker extends ResourceStateChecker
{
/**
* Initializes checker.
*
* @param FileResource $resource
* @param integer $eventsMask event types bitmask
*/
public function __construct(FileResource $resource, $eventsMask = FilesystemEvent::ALL)
{
parent::__construct($resource, $eventsMask);
}
}
<?php
namespace Lurker\StateChecker\Inotify;
/**
* Bag for the inotify resource state checkers.
*
* @author Yaroslav Kiliba <om.dattaya@gmail.com>
*/
class CheckerBag
{
/**
* @var \SplObjectStorage[]
*/
protected $watched = array();
/**
* @var resource Inotify resource.
*/
private $inotify;
/**
* Initializes bag.
*
* @param resource $inotify Inotify resource
*/
public function __construct($inotify)
{
$this->inotify = $inotify;
}
/**
* Adds state checker to the bag.
*
* @param ResourceStateChecker $watched
*/
public function add(ResourceStateChecker $watched)
{
$id = $watched->getId();
if (!isset($this->watched[$id])) {
$this->watched[$id] = new \SplObjectStorage();
}
$this->watched[$id]->attach($watched);
}
/**
* Returns state checker from the bag
*
* @param int $id Watch descriptor
*
* @return \SplObjectStorage|array
*/
public function get($id)
{
return isset($this->watched[$id]) ? $this->watched[$id] : array();
}
/**
* Checks whether at least one state checker with id $id exists.
*
* @param int $id Watch descriptor
*
* @return bool
*/
public function has($id)
{
return isset($this->watched[$id]) && 0 !== $this->watched[$id]->count();
}
/**
* @return resource Inotify resource
*/
public function getInotify()
{
return $this->inotify;
}
/**
* Removes state checker from the bag
*
* @param ResourceStateChecker $watched
*/
public function remove(ResourceStateChecker $watched)
{
$this->watched[$watched->getId()]->detach($watched);
}
}
<?php
namespace Lurker\StateChecker\Inotify;
use Lurker\Resource\DirectoryResource;
use Lurker\Resource\FileResource;
use Lurker\Event\FilesystemEvent;
/**
* Directory state checker.
*
* @author Yaroslav Kiliba <om.dattaya@gmail.com>
*/
class DirectoryStateChecker extends ResourceStateChecker
{
/**
* @var DirectoryStateChecker[]
*/
protected $directories = array();
/**
* @var FileResource[]
*/
protected $files = array();
/**
* @var array File inotify events
*/
protected $fileEvents = array();
/**
* @var array Dir inotify events
*/
protected $dirEvents = array();
/**
* @var array It is used to track resource moving
* @see DirectoryStateChecker::trackMoveEvent()
*/
protected $movedResources = array();
/**
* @var string Key in the $movedResources array where to put name of the resource from next following move event.
* @see DirectoryStateChecker::trackMoveEvent()
*/
protected $lastMove;
/**
* @var bool
*/
protected $isNew = false;
/**
* Initializes checker.
*
* @param CheckerBag $bag
* @param DirectoryResource $resource
* @param int $eventsMask
*/
public function __construct(CheckerBag $bag, DirectoryResource $resource, $eventsMask = FilesystemEvent::ALL)
{
parent::__construct($bag, $resource, $eventsMask);
$this->createChildCheckers();
}
/**
* {@inheritdoc}
*/
public function setEvent($mask, $name = '')
{
if ($this->isDir($mask)) {
if (0 !== (IN_ATTRIB & $mask)) {
return;
}
$this->dirEvents[$name] = $mask;
} else {
$this->fileEvents[$name] = $mask;
}
$this->trackMoveEvent($mask, $name);
}
/**
* {@inheritdoc}
*/
public function getChangeset()
{
$this->event = isset($this->fileEvents['']) ? $this->fileEvents[''] : null;
unset($this->fileEvents['']);
$this->handleItself();
$changeset = array();
if ($this->event) {
if ($event = $this->fromInotifyMask($this->event)) {
$changeset[] = array(
'resource' => $this->getResource(),
'event' => $event
);
}
if ($this->isDeleted($this->event)) {
$this->fileEvents = array_fill_keys(array_keys($this->files), IN_DELETE);
$this->dirEvents = array_fill_keys(array_keys($this->directories), IN_DELETE);
$this->getBag()->remove($this);
$this->id = null;
}
}
$deleted = array();
foreach ($this->movedResources as $key => $value) {
if ($key === $value) {
unset($this->dirEvents[$key]);
unset($this->fileEvents[$key]);
}
}
foreach ($this->dirEvents as $name => $event) {
$normalized = $this->normalizeEvent($event);
if (isset($this->directories[$name])) {
$this->directories[$name]->setEvent($normalized);
if ($this->isDeleted($normalized)) {
$deleted[] = $this->directories[$name];
unset($this->directories[$name]);
}
} elseif (!$this->isDeleted($normalized)) {
$this->createNewDirectoryChecker($name);
}
}
foreach ($this->fileEvents as $name => $event) {
$normalized = $this->normalizeFileEvent($event, $name);
if (($event = $this->fromInotifyMask($normalized)) && $this->files[$name] instanceof FileResource) {
$changeset[] =
array(
'resource' => $this->files[$name],
'event' => $event
);
}
if ($this->isDeleted($normalized)) {
unset($this->files[$name]);
}
}
$funct = function($checker) use (&$changeset) {
foreach ($checker->getChangeset() as $change) {
$changeset[] = $change;
}
};
array_walk($this->directories, $funct);
array_walk($deleted, $funct);
$this->dirEvents = $this->fileEvents = $this->movedResources = array();
$this->event = null;
return $changeset;
}
/**
* Tracks move event. It is for situation when resource was roundtripped, e.g.
* rename('dir', 'dir_new'); rename('dir_new', 'dir'). As a result no events should be returned.
* This function just keeps track of the move events, and they're analyzed in the getChangeset method.
*
* @param int $mask
* @param string $name
*/
protected function trackMoveEvent($mask, $name)
{
if ($this->isMovedFrom($mask)) {
if ($key = array_search($name, $this->movedResources)) {
$this->lastMove = $key;
} else {
$this->lastMove = $name;
}
} elseif ($this->isMovedTo($mask)) {
$this->movedResources[$this->lastMove] = $name;
} elseif ($key = array_search($name, $this->movedResources)) {
unset($this->movedResources[$key]);
}
}
/**
* Handles event related to itself.
*/
protected function handleItself()
{
if (!$this->isNew && $this->isCreated($this->event)) {
$this->unwatch($this->id);
$this->reindexChildCheckers();
$this->event = null;
}
$this->isNew = false;
}
/**
* Reads files and subdirectories and transforms them to resources.
*/
protected function createChildCheckers()
{
foreach ($this->getResource()->getFilteredResources() as $resource) {
$resource instanceof DirectoryResource
? $this->directories[basename((string) $resource)] = new DirectoryStateChecker($this->getBag(), $resource, $this->getEventsMask())
: $this->files[basename((string) $resource)] = $resource;
}
}
/**
* Used in case the folder was deleted and than created again or situations like this.
* It rescans the folder, files that was before get IN_MODIFY event, folders - IN_CREATE - to make them to rescan itself
*/
protected function reindexChildCheckers()
{
$this->fileEvents = array_fill_keys(array_keys($this->files), IN_DELETE);
$this->dirEvents = array_fill_keys(array_keys($this->directories), IN_DELETE);
foreach ($this->getResource()->getFilteredResources() as $resource) {
$basename = basename((string) $resource);
if ($resource instanceof FileResource) {
if (isset($this->files[$basename])) {
$this->fileEvents[$basename] = IN_MODIFY;
} else {
$this->files[$basename] = $resource;
$this->fileEvents[$basename] = 'new';
}
} else {
isset($this->directories[$basename])
? $this->dirEvents[$basename] = IN_CREATE
: $this->createNewDirectoryChecker($basename, $resource);
}
}
$this->watch();
}
/**
* Normalizes file event
*
* @param int $event
* @param string $name
*
* @return null|int
*/
protected function normalizeFileEvent($event, $name)
{
if ('new' === $event) {
return IN_CREATE;
}
$event = $this->normalizeEvent($event);
if (isset($this->files[$name])) {
return $this->isCreated($event) ? IN_MODIFY : $event;
}
if (!$this->isDeleted($event)) {
$this->createFileResource($name);
return IN_CREATE;
}
return null;
}
/**
* Normalizes event
*
* @param int $event
*
* @return int
*/
protected function normalizeEvent($event)
{
$event &= ~IN_ISDIR;
if (0 !== ($event & IN_MOVED_FROM)) {
return IN_DELETE;
} elseif (0 !== ($event & IN_MOVED_TO)) {
return IN_CREATE;
}
return $event;
}
/**
* Creates new DirectoryStateChecker
*
* @param string $name
* @param null|DirectoryResource $resource
*/
protected function createNewDirectoryChecker($name, DirectoryResource $resource = null)
{
$resource = $resource ?: new DirectoryResource($this->getResource()->getResource().'/'.$name);
$this->directories[$name] = new NewDirectoryStateChecker($this->getBag(), $resource, $this->getEventsMask());
}
/**
* Creates new FileResource
*
* @param string $name
*/
protected function createFileResource($name)
{
if ($this->getResource()->getPattern() && !preg_match($this->getResource()->getPattern(), $name)) {
$this->files[$name] = 'skip';
} else {
$this->files[$name] = new FileResource($this->getResource()->getResource().'/'.$name);
}
}
}
<?php
namespace Lurker\StateChecker\Inotify;
use Lurker\Resource\FileResource;
use Lurker\Event\FilesystemEvent;
/**
* File state checker.
*
* @author Yaroslav Kiliba <om.dattaya@gmail.com>
*/
class FileStateChecker extends ResourceStateChecker
{
/**
* Initializes checker.
*
* @param CheckerBag $bag
* @param FileResource $resource
* @param int $eventsMask
*/
public function __construct(CheckerBag $bag, FileResource $resource, $eventsMask = FilesystemEvent::ALL)
{
parent::__construct($bag, $resource, $eventsMask);
}
/**
* {@inheritdoc}
*/
public function setEvent($mask, $name = '')
{
$this->event = $mask;
}
/**
* {@inheritdoc}
*/
public function getChangeset()
{
$changeset = array();
$this->handleItself();
if ($this->fromInotifyMask($this->event)) {
$changeset[] =
array(
'resource' => $this->getResource(),
'event' => $this->fromInotifyMask($this->event)
);
}
$this->setEvent(false);
return $changeset;
}
/**
* Handles event related to itself.
*/
protected function handleItself()
{
if ($this->isMoved($this->event)) {
if ($this->getResource()->exists() && $this->addWatch() === $this->id) {
return;
}
$this->unwatch($this->id);
}
if ($this->getResource()->exists()) {
if ($this->isIgnored($this->event) || $this->isMoved($this->event) || !$this->id) {
$this->setEvent($this->id ? IN_MODIFY : IN_CREATE);
$this->watch();
}
} elseif ($this->id) {
$this->event = IN_DELETE;
$this->getBag()->remove($this);
$this->unwatch($this->id);
$this->id = null;
}
}
}
<?php
namespace Lurker\StateChecker\Inotify;
use Lurker\Resource\DirectoryResource;
use Lurker\Event\FilesystemEvent;
/**
* Directory state checker. Sets for itself and children a flag that indicates newness.
*
* @author Yaroslav Kiliba <om.dattaya@gmail.com>
*/
class NewDirectoryStateChecker extends DirectoryStateChecker
{
/**
* @var bool|null
*/
protected $isNew = true;
/**
* Initializes checker.
*
* @param CheckerBag $bag
* @param DirectoryResource $resource
* @param int $eventsMask
*/
public function __construct(CheckerBag $bag, DirectoryResource $resource, $eventsMask = FilesystemEvent::ALL)
{
$this->setEvent(IN_CREATE);
parent::__construct($bag, $resource, $eventsMask);
}
/**
* {@inheritdoc}
*/
protected function createChildCheckers()
{
foreach ($this->getResource()->getFilteredResources() as $resource) {
$basename = basename((string) $resource);
if ($resource instanceof DirectoryResource) {
$this->createNewDirectoryChecker($basename, $resource);
} else {
$this->files[$basename] = $resource;
$this->fileEvents[$basename] = 'new';
}
}
}
}
<?php
namespace Lurker\StateChecker\Inotify;
use Lurker\Event\FilesystemEvent;
use Lurker\Resource\ResourceInterface;
use Lurker\StateChecker\StateCheckerInterface;
/**
* Abstract resource state checker.
*
* @author Yaroslav Kiliba <om.dattaya@gmail.com>
*/
abstract class ResourceStateChecker implements StateCheckerInterface
{
/**
* @var int Watch descriptor
*/
protected $id;
/**
* @var int Inotify event
*/
protected $event;
/**
* @var CheckerBag
*/
private $bag;
/**
* @var int
*/
private $eventsMask;
/**
* @var ResourceInterface
*/
private $resource;
/**
* Initializes checker.
*
* @param CheckerBag $bag
* @param ResourceInterface $resource
* @param int $eventsMask
*/
public function __construct(CheckerBag $bag, ResourceInterface $resource, $eventsMask = FilesystemEvent::ALL)
{
$this->resource = $resource;
$this->eventsMask = $eventsMask;
$this->bag = $bag;
$this->watch();
}
/**
* Returns tracked resource.
*
* @return ResourceInterface
*/
public function getResource()
{
return $this->resource;
}
/**
* Returns watch descriptor
*
* @return int
*/
public function getId()
{
return $this->id;
}
/**
* Allows to set event for resource itself or for child resources.
*
* @param int $mask
* @param string $name
*/
abstract public function setEvent($mask, $name = '');
/**
* Returns events mask for checker.
*
* @return int
*/
protected function getEventsMask()
{
return $this->eventsMask;
}
/**
* @return CheckerBag
*/
protected function getBag()
{
return $this->bag;
}
/**
* Starts to track current resource
*/
protected function watch()
{
if ($this->id) {
$this->bag->remove($this);
}
$this->id = $this->addWatch();
$this->bag->add($this);
}
/**
* Watch resource
*
* @return int
*/
protected function addWatch()
{
return inotify_add_watch($this->getBag()->getInotify(), $this->getResource()->getResource(), $this->getInotifyEventMask());
}
/**
* Unwatch resource
*
* @param int $id Watch descriptor
*/
protected function unwatch($id)
{
@inotify_rm_watch($this->bag->getInotify(), $id);
}
/**
* Transforms inotify event to FilesystemEvent event
*
* @param int $mask
*
* @return bool|int Returns event only if the checker supports it.
*/
protected function fromInotifyMask($mask)
{
$mask &= ~IN_ISDIR;
$event = 0;
switch ($mask) {
case (IN_MODIFY):
case (IN_ATTRIB):
$event = FilesystemEvent::MODIFY;
break;
case (IN_CREATE):
$event = FilesystemEvent::CREATE;
break;
case (IN_DELETE):
case (IN_IGNORED):
$event = FilesystemEvent::DELETE;
}
return $this->supportsEvent($event) ? $event : false;
}
/**
* Checks whether checker supports provided resource event.
*
* @param int $event
*
* @return bool
*/
protected function supportsEvent($event)
{
return 0 !== ($this->eventsMask & $event);
}
/**
* Inotify event mask for inotify_add_watch
*
* @return int
*/
protected function getInotifyEventMask()
{
return IN_MODIFY | IN_ATTRIB | IN_DELETE | IN_CREATE | IN_MOVE | IN_MOVE_SELF;
}
/**
* Returns true if it is a directory mask
*
* @param int $mask
*
* @return bool
*/
protected function isDir($mask)
{
return 0 !== ($mask & IN_ISDIR);
}
/**
* Returns true if it is a mask with a IN_DELETE bit active
*
* @param int $mask
*
* @return bool
*/
protected function isDeleted($mask)
{
return 0 !== ($mask & IN_DELETE);
}
/**
* Returns true if it is a IN_IGNORED mask
*
* @param int $mask
*
* @return bool
*/
protected function isIgnored($mask)
{
return IN_IGNORED === $mask;
}
/**
* Returns true if it is a IN_MOVE_SELF mask
*
* @param int $mask
*
* @return bool
*/
protected function isMoved($mask)
{
return IN_MOVE_SELF === $mask;
}
/**
* Returns true if it is a mask with a IN_CREATE bit active
*
* @param int $mask
*
* @return bool
*/
protected function isCreated($mask)
{
return 0 !== ($mask & IN_CREATE);
}
/**
* Returns true if it is a mask with a IN_MOVED_FROM bit active
*
* @param int $mask
*
* @return bool
*/
protected function isMovedFrom($mask)
{
return 0 !== ($mask & IN_MOVED_FROM);
}
/**
* Returns true if it is a mask with a IN_MOVED_TO bit active
*
* @param int $mask
*
* @return bool
*/
protected function isMovedTo($mask)
{
return 0 !== ($mask & IN_MOVED_TO);
}
}
<?php
namespace Lurker\StateChecker\Inotify;
/**
* Topmost directory state checker. Top directory - folder that was provided to InotifyTracker::track method.
*
* @author Yaroslav Kiliba <om.dattaya@gmail.com>
*/
class TopDirectoryStateChecker extends DirectoryStateChecker
{
/**
* {@inheritdoc}
*/
protected function handleItself()
{
if ($this->getResource()->exists()) {
if ($this->isMoved($this->event)) {
if ($this->id !== ($id = $this->addWatch())) {
$this->unwatch($this->id);
$this->reindexChildCheckers();
if ($this->getBag()->has($id)) {
$this->unwatch($id);
}
}
return;
}
if ($this->isIgnored($this->event) || !$this->id) {
$this->event = $this->id ? null : IN_CREATE;
$this->reindexChildCheckers();
}
} elseif ($this->id) {
$this->event = IN_DELETE;
$this->unwatch($this->id);
}
}
}
<?php
namespace Lurker\StateChecker;
use Lurker\Event\FilesystemEvent;
use Lurker\Resource\DirectoryResource;
/**
* Recursive directory state checker.
*
* @author Konstantin Kudryashov <ever.zet@gmail.com>
*/
class NewDirectoryStateChecker extends ResourceStateChecker
{
protected $childs = array();
/**
* Initializes checker.
*
* @param DirectoryResource $resource
* @param integer $eventsMask event types bitmask
*/
public function __construct(DirectoryResource $resource, $eventsMask = FilesystemEvent::ALL)
{
parent::__construct($resource, $eventsMask);
}
/**
* {@inheritdoc}
*/
public function getChangeset()
{
$changeset = parent::getChangeset();
// remove directory modification from changeset
if (isset($changeset[0]) && FilesystemEvent::MODIFY === $changeset[0]['event']) {
$changeset = array();
}
// check for changes in already added subfolders/files
foreach ($this->childs as $id => $checker) {
foreach ($checker->getChangeset() as $change) {
if ($this->supportsEvent($change['event'])) {
$changeset[] = $change;
}
}
// remove checkers for removed resources
if (!$checker->getResource()->exists()) {
unset($this->childs[$id]);
}
}
// check for new subfolders/files
if ($this->getResource()->exists()) {
foreach ($this->createNewDirectoryChildCheckers($this->getResource()) as $checker) {
$resource = $checker->getResource();
$resourceId = $resource->getId();
if (!isset($this->childs[$resourceId])) {
$this->childs[$resourceId] = $checker;
if ($this->supportsEvent($event = FilesystemEvent::CREATE)) {
$changeset[] = array(
'event' => $event,
'resource' => $resource
);
}
// check for new directory changes
if ($checker instanceof NewDirectoryStateChecker) {
foreach ($checker->getChangeset() as $change) {
if ($this->supportsEvent($change['event'])) {
$changeset[] = $change;
}
}
}
}
}
}
return $changeset;
}
/**
* Reads files and subdirectories on provided resource path and transform them to resources.
*
* @param DirectoryResource $resource
*
* @return array
*/
protected function createDirectoryChildCheckers(DirectoryResource $resource)
{
$checkers = array();
foreach ($resource->getFilteredResources() as $resource) {
if ($resource instanceof DirectoryResource) {
$checkers[] = new DirectoryStateChecker($resource, $this->getEventsMask());
} else {
$checkers[] = new FileStateChecker($resource, $this->getEventsMask());
}
}
return $checkers;
}
/**
* Reads files and subdirectories on provided resource path and transform them to resources.
*
* @param DirectoryResource $resource
*
* @return array
*/
protected function createNewDirectoryChildCheckers(DirectoryResource $resource)
{
$checkers = array();
foreach ($resource->getFilteredResources() as $resource) {
if ($resource instanceof DirectoryResource) {
$checkers[] = new NewDirectoryStateChecker($resource, $this->getEventsMask());
} else {
$checkers[] = new FileStateChecker($resource, $this->getEventsMask());
}
}
return $checkers;
}
}
<?php
namespace Lurker\StateChecker;
use Lurker\Event\FilesystemEvent;
use Lurker\Resource\ResourceInterface;
/**
* Abstract resource state checker class.
*
* @author Konstantin Kudryashov <ever.zet@gmail.com>
*/
abstract class ResourceStateChecker implements StateCheckerInterface
{
private $resource;
private $timestamp;
private $eventsMask;
private $deleted = false;
/**
* Initializes checker.
*
* @param ResourceInterface $resource resource
* @param integer $eventsMask event types bitmask
*/
public function __construct(ResourceInterface $resource, $eventsMask = FilesystemEvent::ALL)
{
$this->resource = $resource;
$this->timestamp = $resource->getModificationTime() + 1;
$this->eventsMask = $eventsMask;
$this->deleted = !$resource->exists();
}
/**
* {@inheritdoc}
*/
public function getResource()
{
return $this->resource;
}
/**
* Returns events mask for checker.
*
* @return integer
*/
public function getEventsMask()
{
return $this->eventsMask;
}
/**
* {@inheritdoc}
*/
public function getChangeset()
{
$changeset = array();
if ($this->deleted) {
if ($this->resource->exists()) {
$this->timestamp = $this->resource->getModificationTime() + 1;
$this->deleted = false;
if ($this->supportsEvent($event = FilesystemEvent::CREATE)) {
$changeset[] = array(
'event' => $event,
'resource' => $this->resource
);
}
}
} elseif (!$this->resource->exists()) {
$this->deleted = true;
if ($this->supportsEvent($event = FilesystemEvent::DELETE)) {
$changeset[] = array(
'event' => $event,
'resource' => $this->resource
);
}
} elseif (!$this->resource->isFresh($this->timestamp)) {
$this->timestamp = $this->resource->getModificationTime() + 1;
if ($this->supportsEvent($event = FilesystemEvent::MODIFY)) {
$changeset[] = array(
'event' => $event,
'resource' => $this->resource
);
}
}
return $changeset;
}
/**
* Checks whether checker supports provided resource event.
*
* @param integer $event
*
* @return Boolean
*/
protected function supportsEvent($event)
{
return 0 !== ($this->eventsMask & $event);
}
/**
* Checks whether resource have been previously deleted.
*
* @return Boolean
*/
protected function isDeleted()
{
return $this->deleted;
}
}
<?php
namespace Lurker\StateChecker;
use Lurker\Resource\ResourceInterface;
/**
* Resource state checker interface.
*
* @author Konstantin Kudryashov <ever.zet@gmail.com>
*/
interface StateCheckerInterface
{
/**
* Returns tracked resource.
*
* @return ResourceInterface
*/
public function getResource();
/**
* Check tracked resource for changes.
*
* @return array
*/
public function getChangeset();
}
<?php
namespace Lurker\Tracker;
use Lurker\Resource\DirectoryResource;
use Lurker\Resource\TrackedResource;
use Lurker\Event\FilesystemEvent;
use Lurker\StateChecker\Inotify\TopDirectoryStateChecker;
use Lurker\StateChecker\Inotify\FileStateChecker;
use Lurker\Exception\RuntimeException;
use Lurker\StateChecker\Inotify\CheckerBag;
/**
* Inotify tracker. To use this tracker you must install inotify extension.
*
* @link http://pecl.php.net/package/inotify Inotify PECL extension
* @author Yaroslav Kiliba <om.dattaya@gmail.com>
*/
class InotifyTracker implements TrackerInterface
{
/**
* @var array
*/
protected $checkers = array();
/**
* @var CheckerBag
*/
protected $bag;
/**
* @var resource Inotify resource.
*/
private $inotify;
/**
* Initializes tracker. Creates inotify resource used to track file and directory changes.
*
* @throws RuntimeException If inotify extension unavailable
*/
public function __construct()
{
if (!function_exists('inotify_init')) {
throw new RuntimeException('You must install inotify to be able to use this tracker.');
}
$this->inotify = inotify_init();
stream_set_blocking($this->inotify, 0);
$this->bag = new CheckerBag($this->inotify);
}
/**
* {@inheritdoc}
*/
public function track(TrackedResource $resource, $eventsMask = FilesystemEvent::ALL)
{
$trackingId = $resource->getTrackingId();
$checker = $resource->getOriginalResource() instanceof DirectoryResource
? new TopDirectoryStateChecker($this->bag, $resource->getOriginalResource(), $eventsMask)
: new FileStateChecker($this->bag, $resource->getOriginalResource(), $eventsMask);
$this->checkers[$trackingId] = array(
'tracked' => $resource,
'checker' => $checker
);
}
/**
* {@inheritdoc}
*
* @throws RuntimeException If event queue overflowed
*/
public function getEvents()
{
$inotifyEvents = $this->readEvents();
$inotifyEvents = is_array($inotifyEvents) ? $inotifyEvents : array();
$last = end($inotifyEvents);
if (IN_Q_OVERFLOW === $last['mask']) {
throw new RuntimeException('Event queue overflowed. Either read events more frequently or increase the limit for queues. The limit can be changed in /proc/sys/fs/inotify/max_queued_events');
}
foreach ($inotifyEvents as $event) {
foreach ($this->bag->get($event['wd']) as $watched) {
$watched->setEvent($event['mask'], $event['name']);
}
}
$events = array();
foreach ($this->checkers as $meta) {
$tracked = $meta['tracked'];
$watched = $meta['checker'];
foreach ($watched->getChangeset() as $change) {
$events[] = new FilesystemEvent($tracked, $change['resource'], $change['event']);
}
}
return $events;
}
/**
* Closes the inotify resource.
*/
public function __destruct()
{
fclose($this->inotify);
}
/**
* Returns all events happened since last event readout
*
* @return array
*/
protected function readEvents()
{
return inotify_read($this->inotify);
}
}
<?php
namespace Lurker\Tracker;
use Lurker\Resource\DirectoryResource;
use Lurker\Event\FilesystemEvent;
use Lurker\Resource\TrackedResource;
use Lurker\StateChecker\DirectoryStateChecker;
use Lurker\StateChecker\FileStateChecker;
/**
* Recursive iterator resources tracker.
*
* @author Konstantin Kudryashov <ever.zet@gmail.com>
*/
class RecursiveIteratorTracker implements TrackerInterface
{
private $checkers = array();
/**
* {@inheritdoc}
*/
public function track(TrackedResource $resource, $eventsMask = FilesystemEvent::ALL)
{
$trackingId = $resource->getTrackingId();
$checker = $resource->getOriginalResource() instanceof DirectoryResource
? new DirectoryStateChecker($resource->getOriginalResource(), $eventsMask)
: new FileStateChecker($resource->getOriginalResource(), $eventsMask);
$this->checkers[$trackingId] = array(
'tracked' => $resource,
'checker' => $checker
);
}
/**
* {@inheritdoc}
*/
public function getEvents()
{
$events = array();
foreach ($this->checkers as $trackingId => $meta) {
$tracked = $meta['tracked'];
$checker = $meta['checker'];
foreach ($checker->getChangeset() as $change) {
$events[] = new FilesystemEvent($tracked, $change['resource'], $change['event']);
}
}
return $events;
}
}
<?php
namespace Lurker\Tracker;
use Lurker\Resource\TrackedResource;
use Lurker\Event\FilesystemEvent;
/**
* Resources tracker interface.
*
* @author Konstantin Kudryashov <ever.zet@gmail.com>
*/
interface TrackerInterface
{
/**
* Starts to track provided resource for changes.
*
* @param TrackedResource $resource
* @param integer $eventsMask event types bitmask
*/
public function track(TrackedResource $resource, $eventsMask = FilesystemEvent::ALL);
/**
* Checks tracked resources for change events.
*
* @return array change events array
*/
public function getEvents();
}
<?php
namespace League\Container\Argument;
use League\Container\ImmutableContainerAwareInterface;
use ReflectionFunctionAbstract;
interface ArgumentResolverInterface extends ImmutableContainerAwareInterface
{
/**
* Resolve an array of arguments to their concrete implementations.
*
* @param array $arguments
* @return array
*/
public function resolveArguments(array $arguments);
/**
* Resolves the correct arguments to be passed to a method.
*
* @param \ReflectionFunctionAbstract $method
* @param array $args
* @return array
*/
public function reflectArguments(ReflectionFunctionAbstract $method, array $args = []);
}
<?php
namespace League\Container\Argument;
use League\Container\Exception\NotFoundException;
use League\Container\ReflectionContainer;
use ReflectionFunctionAbstract;
use ReflectionParameter;
trait ArgumentResolverTrait
{
/**
* {@inheritdoc}
*/
public function resolveArguments(array $arguments)
{
foreach ($arguments as &$arg) {
if ($arg instanceof RawArgumentInterface) {
$arg = $arg->getValue();
continue;
}
if (! is_string($arg)) {
continue;
}
$container = $this->getContainer();
if (is_null($container) && $this instanceof ReflectionContainer) {
$container = $this;
}
if (! is_null($container) && $container->has($arg)) {
$arg = $container->get($arg);
if ($arg instanceof RawArgumentInterface) {
$arg = $arg->getValue();
}
continue;
}
}
return $arguments;
}
/**
* {@inheritdoc}
*/
public function reflectArguments(ReflectionFunctionAbstract $method, array $args = [])
{
$arguments = array_map(function (ReflectionParameter $param) use ($method, $args) {
$name = $param->getName();
$class = $param->getClass();
if (array_key_exists($name, $args)) {
return $args[$name];
}
if (! is_null($class)) {
return $class->getName();
}
if ($param->isDefaultValueAvailable()) {
return $param->getDefaultValue();
}
throw new NotFoundException(sprintf(
'Unable to resolve a value for parameter (%s) in the function/method (%s)',
$name,
$method->getName()
));
}, $method->getParameters());
return $this->resolveArguments($arguments);
}
/**
* @return ContainerInterface
*/
abstract public function getContainer();
}
<?php
namespace League\Container\Argument;
class RawArgument implements RawArgumentInterface
{
/**
* @var mixed
*/
protected $value;
/**
* {@inheritdoc}
*/
public function __construct($value)
{
$this->value = $value;
}
/**
* {@inheritdoc}
*/
public function getValue()
{
return $this->value;
}
}
<?php
namespace League\Container\Argument;
interface RawArgumentInterface
{
/**
* Return the value of the raw argument.
*
* @return mixed
*/
public function getValue();
}
<?php
namespace League\Container;
use Interop\Container\ContainerInterface as InteropContainerInterface;
use League\Container\Definition\DefinitionFactory;
use League\Container\Definition\DefinitionFactoryInterface;
use League\Container\Definition\DefinitionInterface;
use League\Container\Exception\NotFoundException;
use League\Container\Inflector\InflectorAggregate;
use League\Container\Inflector\InflectorAggregateInterface;
use League\Container\ServiceProvider\ServiceProviderAggregate;
use League\Container\ServiceProvider\ServiceProviderAggregateInterface;
class Container implements ContainerInterface
{
/**
* @var \League\Container\Definition\DefinitionFactoryInterface
*/
protected $definitionFactory;
/**
* @var \League\Container\Definition\DefinitionInterface[]
*/
protected $definitions = [];
/**
* @var \League\Container\Definition\DefinitionInterface[]
*/
protected $sharedDefinitions = [];
/**
* @var \League\Container\Inflector\InflectorAggregateInterface
*/
protected $inflectors;
/**
* @var \League\Container\ServiceProvider\ServiceProviderAggregateInterface
*/
protected $providers;
/**
* @var array
*/
protected $shared = [];
/**
* @var \Interop\Container\ContainerInterface[]
*/
protected $delegates = [];
/**
* Constructor.
*
* @param \League\Container\ServiceProvider\ServiceProviderAggregateInterface|null $providers
* @param \League\Container\Inflector\InflectorAggregateInterface|null $inflectors
* @param \League\Container\Definition\DefinitionFactoryInterface|null $definitionFactory
*/
public function __construct(
ServiceProviderAggregateInterface $providers = null,
InflectorAggregateInterface $inflectors = null,
DefinitionFactoryInterface $definitionFactory = null
) {
// set required dependencies
$this->providers = (is_null($providers))
? (new ServiceProviderAggregate)->setContainer($this)
: $providers->setContainer($this);
$this->inflectors = (is_null($inflectors))
? (new InflectorAggregate)->setContainer($this)
: $inflectors->setContainer($this);
$this->definitionFactory = (is_null($definitionFactory))
? (new DefinitionFactory)->setContainer($this)
: $definitionFactory->setContainer($this);
}
/**
* {@inheritdoc}
*/
public function get($alias, array $args = [])
{
$service = $this->getFromThisContainer($alias, $args);
if ($service === false && $this->providers->provides($alias)) {
$this->providers->register($alias);
$service = $this->getFromThisContainer($alias, $args);
}
if ($service !== false) {
return $service;
}
if ($resolved = $this->getFromDelegate($alias, $args)) {
return $this->inflectors->inflect($resolved);
}
throw new NotFoundException(
sprintf('Alias (%s) is not being managed by the container', $alias)
);
}
/**
* {@inheritdoc}
*/
public function has($alias)
{
if (array_key_exists($alias, $this->definitions) || $this->hasShared($alias)) {
return true;
}
if ($this->providers->provides($alias)) {
return true;
}
return $this->hasInDelegate($alias);
}
/**
* Returns a boolean to determine if the container has a shared instance of an alias.
*
* @param string $alias
* @param boolean $resolved
* @return boolean
*/
public function hasShared($alias, $resolved = false)
{
$shared = ($resolved === false) ? array_merge($this->shared, $this->sharedDefinitions) : $this->shared;
return (array_key_exists($alias, $shared));
}
/**
* {@inheritdoc}
*/
public function add($alias, $concrete = null, $share = false)
{
if (is_null($concrete)) {
$concrete = $alias;
}
$definition = $this->definitionFactory->getDefinition($alias, $concrete);
if ($definition instanceof DefinitionInterface) {
if ($share === false) {
$this->definitions[$alias] = $definition;
} else {
$this->sharedDefinitions[$alias] = $definition;
}
return $definition;
}
// dealing with a value that cannot build a definition
$this->shared[$alias] = $concrete;
}
/**
* {@inheritdoc}
*/
public function share($alias, $concrete = null)
{
return $this->add($alias, $concrete, true);
}
/**
* {@inheritdoc}
*/
public function addServiceProvider($provider)
{
$this->providers->add($provider);
return $this;
}
/**
* {@inheritdoc}
*/
public function extend($alias)
{
if ($this->providers->provides($alias)) {
$this->providers->register($alias);
}
if (array_key_exists($alias, $this->definitions)) {
return $this->definitions[$alias];
}
if (array_key_exists($alias, $this->sharedDefinitions)) {
return $this->sharedDefinitions[$alias];
}
throw new NotFoundException(
sprintf('Unable to extend alias (%s) as it is not being managed as a definition', $alias)
);
}
/**
* {@inheritdoc}
*/
public function inflector($type, callable $callback = null)
{
return $this->inflectors->add($type, $callback);
}
/**
* {@inheritdoc}
*/
public function call(callable $callable, array $args = [])
{
return (new ReflectionContainer)->setContainer($this)->call($callable, $args);
}
/**
* Delegate a backup container to be checked for services if it
* cannot be resolved via this container.
*
* @param \Interop\Container\ContainerInterface $container
* @return $this
*/
public function delegate(InteropContainerInterface $container)
{
$this->delegates[] = $container;
if ($container instanceof ImmutableContainerAwareInterface) {
$container->setContainer($this);
}
return $this;
}
/**
* Returns true if service is registered in one of the delegated backup containers.
*
* @param string $alias
* @return boolean
*/
public function hasInDelegate($alias)
{
foreach ($this->delegates as $container) {
if ($container->has($alias)) {
return true;
}
}
return false;
}
/**
* Attempt to get a service from the stack of delegated backup containers.
*
* @param string $alias
* @param array $args
* @return mixed
*/
protected function getFromDelegate($alias, array $args = [])
{
foreach ($this->delegates as $container) {
if ($container->has($alias)) {
return $container->get($alias, $args);
}
continue;
}
return false;
}
/**
* Get a service that has been registered in this container.
*
* @param string $alias
* @param array $args
* @return mixed
*/
protected function getFromThisContainer($alias, array $args = [])
{
if ($this->hasShared($alias, true)) {
return $this->inflectors->inflect($this->shared[$alias]);
}
if (array_key_exists($alias, $this->sharedDefinitions)) {
$shared = $this->inflectors->inflect($this->sharedDefinitions[$alias]->build());
$this->shared[$alias] = $shared;
return $shared;
}
if (array_key_exists($alias, $this->definitions)) {
return $this->inflectors->inflect(
$this->definitions[$alias]->build($args)
);
}
return false;
}
}
<?php
namespace League\Container;
interface ContainerAwareInterface
{
/**
* Set a container
*
* @param \League\Container\ContainerInterface $container
*/
public function setContainer(ContainerInterface $container);
/**
* Get the container
*
* @return \League\Container\ContainerInterface
*/
public function getContainer();
}
<?php
namespace League\Container;
trait ContainerAwareTrait
{
/**
* @var \League\Container\ContainerInterface
*/
protected $container;
/**
* Set a container.
*
* @param \League\Container\ContainerInterface $container
* @return $this
*/
public function setContainer(ContainerInterface $container)
{
$this->container = $container;
return $this;
}
/**
* Get the container.
*
* @return \League\Container\ContainerInterface
*/
public function getContainer()
{
return $this->container;
}
}
<?php
namespace League\Container;
interface ContainerInterface extends ImmutableContainerInterface
{
/**
* Add an item to the container.
*
* @param string $alias
* @param mixed|null $concrete
* @param boolean $share
* @return \League\Container\Definition\DefinitionInterface
*/
public function add($alias, $concrete = null, $share = false);
/**
* Convenience method to add an item to the container as a shared item.
*
* @param string $alias
* @param mixed|null $concrete
* @return \League\Container\Definition\DefinitionInterface
*/
public function share($alias, $concrete = null);
/**
* Add a service provider to the container.
*
* @param string|\League\Container\ServiceProvider\ServiceProviderInterface $provider
* @return void
*/
public function addServiceProvider($provider);
/**
* Returns a definition of an item to be extended.
*
* @param string $alias
* @return \League\Container\Definition\DefinitionInterface
*/
public function extend($alias);
/**
* Allows for manipulation of specific types on resolution.
*
* @param string $type
* @param callable|null $callback
* @return \League\Container\Inflector\Inflector|void
*/
public function inflector($type, callable $callback = null);
/**
* Invoke a callable via the container.
*
* @param callable $callable
* @param array $args
* @return mixed
*/
public function call(callable $callable, array $args = []);
}
<?php
namespace League\Container\Definition;
use League\Container\Argument\ArgumentResolverInterface;
use League\Container\Argument\ArgumentResolverTrait;
use League\Container\ImmutableContainerAwareTrait;
abstract class AbstractDefinition implements ArgumentResolverInterface, DefinitionInterface
{
use ArgumentResolverTrait;
use ImmutableContainerAwareTrait;
/**
* @var string
*/
protected $alias;
/**
* @var mixed
*/
protected $concrete;
/**
* @var array
*/
protected $arguments = [];
/**
* Constructor.
*
* @param string $alias
* @param mixed $concrete
*/
public function __construct($alias, $concrete)
{
$this->alias = $alias;
$this->concrete = $concrete;
}
/**
* {@inheritdoc}
*/
public function withArgument($arg)
{
$this->arguments[] = $arg;
return $this;
}
/**
* {@inheritdoc}
*/
public function withArguments(array $args)
{
foreach ($args as $arg) {
$this->withArgument($arg);
}
return $this;
}
}
<?php
namespace League\Container\Definition;
class CallableDefinition extends AbstractDefinition
{
/**
* {@inheritdoc}
*/
public function build(array $args = [])
{
$args = (empty($args)) ? $this->arguments : $args;
$resolved = $this->resolveArguments($args);
if (is_array($this->concrete) && is_string($this->concrete[0])) {
$this->concrete[0] = ($this->getContainer()->has($this->concrete[0]))
? $this->getContainer()->get($this->concrete[0])
: $this->concrete[0];
}
return call_user_func_array($this->concrete, $resolved);
}
}
<?php
namespace League\Container\Definition;
use ReflectionClass;
class ClassDefinition extends AbstractDefinition implements ClassDefinitionInterface
{
/**
* @var array
*/
protected $methods = [];
/**
* {@inheritdoc}
*/
public function withMethodCall($method, array $args = [])
{
$this->methods[] = [
'method' => $method,
'arguments' => $args
];
return $this;
}
/**
* {@inheritdoc}
*/
public function withMethodCalls(array $methods = [])
{
foreach ($methods as $method => $args) {
$this->withMethodCall($method, $args);
}
return $this;
}
/**
* {@inheritdoc}
*/
public function build(array $args = [])
{
$args = (empty($args)) ? $this->arguments : $args;
$resolved = $this->resolveArguments($args);
$reflection = new ReflectionClass($this->concrete);
$instance = $reflection->newInstanceArgs($resolved);
return $this->invokeMethods($instance);
}
/**
* Invoke methods on resolved instance.
*
* @param object $instance
* @return object
*/
protected function invokeMethods($instance)
{
foreach ($this->methods as $method) {
$args = $this->resolveArguments($method['arguments']);
call_user_func_array([$instance, $method['method']], $args);
}
return $instance;
}
}
<?php
namespace League\Container\Definition;
interface ClassDefinitionInterface extends DefinitionInterface
{
/**
* Add a method to be invoked
*
* @param string $method
* @param array $args
* @return $this
*/
public function withMethodCall($method, array $args = []);
/**
* Add multiple methods to be invoked
*
* @param array $methods
* @return $this
*/
public function withMethodCalls(array $methods = []);
}
<?php
namespace League\Container\Definition;
use League\Container\ImmutableContainerAwareTrait;
class DefinitionFactory implements DefinitionFactoryInterface
{
use ImmutableContainerAwareTrait;
/**
* {@inheritdoc}
*/
public function getDefinition($alias, $concrete)
{
if (is_callable($concrete)) {
return (new CallableDefinition($alias, $concrete))->setContainer($this->getContainer());
}
if (is_string($concrete) && class_exists($concrete)) {
return (new ClassDefinition($alias, $concrete))->setContainer($this->getContainer());
}
// if the item is not defineable we just return the value to be stored
// in the container as an arbitrary value/instance
return $concrete;
}
}
<?php
namespace League\Container\Definition;
use League\Container\ImmutableContainerAwareInterface;
interface DefinitionFactoryInterface extends ImmutableContainerAwareInterface
{
/**
* Return a definition based on type of concrete.
*
* @param string $alias
* @param mixed $concrete
* @return mixed
*/
public function getDefinition($alias, $concrete);
}
<?php
namespace League\Container\Definition;
interface DefinitionInterface
{
/**
* Handle instantiation and manipulation of value and return.
*
* @param array $args
* @return mixed
*/
public function build(array $args = []);
/**
* Add an argument to be injected.
*
* @param mixed $arg
* @return $this
*/
public function withArgument($arg);
/**
* Add multiple arguments to be injected.
*
* @param array $args
* @return $this
*/
public function withArguments(array $args);
}
<?php
namespace League\Container\Exception;
use Interop\Container\Exception\NotFoundException as NotFoundExceptionInterface;
use InvalidArgumentException;
class NotFoundException extends InvalidArgumentException implements NotFoundExceptionInterface
{
}
<?php
namespace League\Container;
use Interop\Container\ContainerInterface as InteropContainerInterface;
interface ImmutableContainerAwareInterface
{
/**
* Set a container
*
* @param \Interop\Container\ContainerInterface $container
*/
public function setContainer(InteropContainerInterface $container);
/**
* Get the container
*
* @return \League\Container\ImmutableContainerInterface
*/
public function getContainer();
}
<?php
namespace League\Container;
use Interop\Container\ContainerInterface as InteropContainerInterface;
trait ImmutableContainerAwareTrait
{
/**
* @var \Interop\Container\ContainerInterface
*/
protected $container;
/**
* Set a container.
*
* @param \Interop\Container\ContainerInterface $container
* @return $this
*/
public function setContainer(InteropContainerInterface $container)
{
$this->container = $container;
return $this;
}
/**
* Get the container.
*
* @return \League\Container\ImmutableContainerInterface
*/
public function getContainer()
{
return $this->container;
}
}
<?php
namespace League\Container;
use Interop\Container\ContainerInterface as InteropContainerInterface;
interface ImmutableContainerInterface extends InteropContainerInterface
{
}
<?php
namespace League\Container\Inflector;
use League\Container\ImmutableContainerAwareTrait;
use League\Container\Argument\ArgumentResolverInterface;
use League\Container\Argument\ArgumentResolverTrait;
class Inflector implements ArgumentResolverInterface
{
use ArgumentResolverTrait;
use ImmutableContainerAwareTrait;
/**
* @var array
*/
protected $methods = [];
/**
* @var array
*/
protected $properties = [];
/**
* Defines a method to be invoked on the subject object.
*
* @param string $name
* @param array $args
* @return $this
*/
public function invokeMethod($name, array $args)
{
$this->methods[$name] = $args;
return $this;
}
/**
* Defines multiple methods to be invoked on the subject object.
*
* @param array $methods
* @return $this
*/
public function invokeMethods(array $methods)
{
foreach ($methods as $name => $args) {
$this->invokeMethod($name, $args);
}
return $this;
}
/**
* Defines a property to be set on the subject object.
*
* @param string $property
* @param mixed $value
* @return $this
*/
public function setProperty($property, $value)
{
$this->properties[$property] = $value;
return $this;
}
/**
* Defines multiple properties to be set on the subject object.
*
* @param array $properties
* @return $this
*/
public function setProperties(array $properties)
{
foreach ($properties as $property => $value) {
$this->setProperty($property, $value);
}
return $this;
}
/**
* Apply inflections to an object.
*
* @param object $object
* @return void
*/
public function inflect($object)
{
$properties = $this->resolveArguments(array_values($this->properties));
$properties = array_combine(array_keys($this->properties), $properties);
foreach ($properties as $property => $value) {
$object->{$property} = $value;
}
foreach ($this->methods as $method => $args) {
$args = $this->resolveArguments($args);
call_user_func_array([$object, $method], $args);
}
}
}
<?php
namespace League\Container\Inflector;
use League\Container\ImmutableContainerAwareTrait;
class InflectorAggregate implements InflectorAggregateInterface
{
use ImmutableContainerAwareTrait;
/**
* @var array
*/
protected $inflectors = [];
/**
* {@inheritdoc}
*/
public function add($type, callable $callback = null)
{
if (is_null($callback)) {
$inflector = new Inflector;
$this->inflectors[$type] = $inflector;
return $inflector;
}
$this->inflectors[$type] = $callback;
}
/**
* {@inheritdoc}
*/
public function inflect($object)
{
foreach ($this->inflectors as $type => $inflector) {
if (! $object instanceof $type) {
continue;
}
if ($inflector instanceof Inflector) {
$inflector->setContainer($this->getContainer());
$inflector->inflect($object);
continue;
}
// must be dealing with a callable as the inflector
call_user_func_array($inflector, [$object]);
}
return $object;
}
}
<?php
namespace League\Container\Inflector;
use League\Container\ImmutableContainerAwareInterface;
interface InflectorAggregateInterface extends ImmutableContainerAwareInterface
{
/**
* Add an inflector to the aggregate.
*
* @param string $type
* @param callable $callback
* @return \League\Container\Inflector\Inflector
*/
public function add($type, callable $callback = null);
/**
* Applies all inflectors to an object.
*
* @param object $object
* @return object
*/
public function inflect($object);
}
<?php
namespace League\Container;
use League\Container\Argument\ArgumentResolverInterface;
use League\Container\Argument\ArgumentResolverTrait;
use League\Container\Exception\NotFoundException;
use ReflectionClass;
use ReflectionException;
use ReflectionFunction;
use ReflectionMethod;
class ReflectionContainer implements
ArgumentResolverInterface,
ImmutableContainerInterface
{
use ArgumentResolverTrait;
use ImmutableContainerAwareTrait;
/**
* {@inheritdoc}
*/
public function get($alias, array $args = [])
{
if (! $this->has($alias)) {
throw new NotFoundException(
sprintf('Alias (%s) is not an existing class and therefore cannot be resolved', $alias)
);
}
$reflector = new ReflectionClass($alias);
$construct = $reflector->getConstructor();
if ($construct === null) {
return new $alias;
}
return $reflector->newInstanceArgs(
$this->reflectArguments($construct, $args)
);
}
/**
* {@inheritdoc}
*/
public function has($alias)
{
return class_exists($alias);
}
/**
* Invoke a callable via the container.
*
* @param callable $callable
* @param array $args
* @return mixed
*/
public function call(callable $callable, array $args = [])
{
if (is_string($callable) && strpos($callable, '::') !== false) {
$callable = explode('::', $callable);
}
if (is_array($callable)) {
$reflection = new ReflectionMethod($callable[0], $callable[1]);
if ($reflection->isStatic()) {
$callable[0] = null;
}
return $reflection->invokeArgs($callable[0], $this->reflectArguments($reflection, $args));
}
$reflection = new ReflectionFunction($callable);
return $reflection->invokeArgs($this->reflectArguments($reflection, $args));
}
}
<?php
namespace League\Container\ServiceProvider;
use League\Container\ContainerAwareTrait;
abstract class AbstractServiceProvider implements ServiceProviderInterface
{
use ContainerAwareTrait;
/**
* @var array
*/
protected $provides = [];
/**
* {@inheritdoc}
*/
public function provides($alias = null)
{
if (! is_null($alias)) {
return (in_array($alias, $this->provides));
}
return $this->provides;
}
}
<?php
namespace League\Container\ServiceProvider;
abstract class AbstractSignatureServiceProvider
extends AbstractServiceProvider
implements SignatureServiceProviderInterface
{
/**
* @var string
*/
protected $signature;
/**
* {@inheritdoc}
*/
public function withSignature($signature)
{
$this->signature = $signature;
return $this;
}
/**
* {@inheritdoc}
*/
public function getSignature()
{
return (is_null($this->signature)) ? get_class($this) : $this->signature;
}
}
<?php
namespace League\Container\ServiceProvider;
interface BootableServiceProviderInterface extends ServiceProviderInterface
{
/**
* Method will be invoked on registration of a service provider implementing
* this interface. Provides ability for eager loading of Service Providers.
*
* @return void
*/
public function boot();
}
<?php
namespace League\Container\ServiceProvider;
use League\Container\ContainerAwareInterface;
use League\Container\ContainerAwareTrait;
class ServiceProviderAggregate implements ServiceProviderAggregateInterface
{
use ContainerAwareTrait;
/**
* @var array
*/
protected $providers = [];
/**
* @var array
*/
protected $registered = [];
/**
* {@inheritdoc}
*/
public function add($provider)
{
if (is_string($provider) && class_exists($provider)) {
$provider = new $provider;
}
if ($provider instanceof ContainerAwareInterface) {
$provider->setContainer($this->getContainer());
}
if ($provider instanceof BootableServiceProviderInterface) {
$provider->boot();
}
if ($provider instanceof ServiceProviderInterface) {
foreach ($provider->provides() as $service) {
$this->providers[$service] = $provider;
}
return $this;
}
throw new \InvalidArgumentException(
'A service provider must be a fully qualified class name or instance ' .
'of (\League\Container\ServiceProvider\ServiceProviderInterface)'
);
}
/**
* {@inheritdoc}
*/
public function provides($service)
{
return array_key_exists($service, $this->providers);
}
/**
* {@inheritdoc}
*/
public function register($service)
{
if (! array_key_exists($service, $this->providers)) {
throw new \InvalidArgumentException(
sprintf('(%s) is not provided by a service provider', $service)
);
}
$provider = $this->providers[$service];
$signature = get_class($provider);
if ($provider instanceof SignatureServiceProviderInterface) {
$signature = $provider->getSignature();
}
// ensure that the provider hasn't already been invoked by any other service request
if (in_array($signature, $this->registered)) {
return;
}
$provider->register();
$this->registered[] = $signature;
}
}
<?php
namespace League\Container\ServiceProvider;
use League\Container\ContainerAwareInterface;
interface ServiceProviderAggregateInterface extends ContainerAwareInterface
{
/**
* Add a service provider to the aggregate.
*
* @param string|\League\Container\ServiceProvider\ServiceProviderInterface $provider
* @return $this
*/
public function add($provider);
/**
* Determines whether a service is provided by the aggregate.
*
* @param string $service
* @return boolean
*/
public function provides($service);
/**
* Invokes the register method of a provider that provides a specific service.
*
* @param string $provider
* @return void
*/
public function register($provider);
}
<?php
namespace League\Container\ServiceProvider;
use League\Container\ContainerAwareInterface;
interface ServiceProviderInterface extends ContainerAwareInterface
{
/**
* Returns a boolean if checking whether this provider provides a specific
* service or returns an array of provided services if no argument passed.
*
* @param string $service
* @return boolean|array
*/
public function provides($service = null);
/**
* Use the register method to register items with the container via the
* protected $this->container property or the `getContainer` method
* from the ContainerAwareTrait.
*
* @return void
*/
public function register();
}
<?php
namespace League\Container\ServiceProvider;
interface SignatureServiceProviderInterface
{
/**
* Set a custom signature for the service provider. This enables
* registering the same service provider multiple times.
*
* @param string $signature
* @return self
*/
public function withSignature($signature);
/**
* The signature of the service provider uniquely identifies it, so
* that we can quickly determine if it has already been registered.
* Defaults to get_class($provider).
*
* @return string
*/
public function getSignature();
}
<?php
/**
* CssMin - A (simple) css minifier with benefits
*
* --
* Copyright (c) 2011 Joe Scylla <joe.scylla@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
* --
*
* @package CssMin
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
/**
* Abstract definition of a CSS token class.
*
* Every token has to extend this class.
*
* @package CssMin/Tokens
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
abstract class aCssToken
{
/**
* Returns the token as string.
*
* @return string
*/
abstract public function __toString();
}
/**
* Abstract definition of a for a ruleset start token.
*
* @package CssMin/Tokens
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
abstract class aCssRulesetStartToken extends aCssToken
{
}
/**
* Abstract definition of a for ruleset end token.
*
* @package CssMin/Tokens
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
abstract class aCssRulesetEndToken extends aCssToken
{
/**
* Implements {@link aCssToken::__toString()}.
*
* @return string
*/
public function __toString()
{
return "}";
}
}
/**
* Abstract definition of a parser plugin.
*
* Every parser plugin have to extend this class. A parser plugin contains the logic to parse one or aspects of a
* stylesheet.
*
* @package CssMin/Parser/Plugins
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
abstract class aCssParserPlugin
{
/**
* Plugin configuration.
*
* @var array
*/
protected $configuration = array();
/**
* The CssParser of the plugin.
*
* @var CssParser
*/
protected $parser = null;
/**
* Plugin buffer.
*
* @var string
*/
protected $buffer = "";
/**
* Constructor.
*
* @param CssParser $parser The CssParser object of this plugin.
* @param array $configuration Plugin configuration [optional]
* @return void
*/
public function __construct(CssParser $parser, array $configuration = null)
{
$this->configuration = $configuration;
$this->parser = $parser;
}
/**
* Returns the array of chars triggering the parser plugin.
*
* @return array
*/
abstract public function getTriggerChars();
/**
* Returns the array of states triggering the parser plugin or FALSE if every state will trigger the parser plugin.
*
* @return array
*/
abstract public function getTriggerStates();
/**
* Parser routine of the plugin.
*
* @param integer $index Current index
* @param string $char Current char
* @param string $previousChar Previous char
* @return mixed TRUE will break the processing; FALSE continue with the next plugin; integer set a new index and break the processing
*/
abstract public function parse($index, $char, $previousChar, $state);
}
/**
* Abstract definition of a minifier plugin class.
*
* Minifier plugin process the parsed tokens one by one to apply changes to the token. Every minifier plugin has to
* extend this class.
*
* @package CssMin/Minifier/Plugins
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
abstract class aCssMinifierPlugin
{
/**
* Plugin configuration.
*
* @var array
*/
protected $configuration = array();
/**
* The CssMinifier of the plugin.
*
* @var CssMinifier
*/
protected $minifier = null;
/**
* Constructor.
*
* @param CssMinifier $minifier The CssMinifier object of this plugin.
* @param array $configuration Plugin configuration [optional]
* @return void
*/
public function __construct(CssMinifier $minifier, array $configuration = array())
{
$this->configuration = $configuration;
$this->minifier = $minifier;
}
/**
* Apply the plugin to the token.
*
* @param aCssToken $token Token to process
* @return boolean Return TRUE to break the processing of this token; FALSE to continue
*/
abstract public function apply(aCssToken &$token);
/**
* --
*
* @return array
*/
abstract public function getTriggerTokens();
}
/**
* Abstract definition of a minifier filter class.
*
* Minifier filters allows a pre-processing of the parsed token to add, edit or delete tokens. Every minifier filter
* has to extend this class.
*
* @package CssMin/Minifier/Filters
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
abstract class aCssMinifierFilter
{
/**
* Filter configuration.
*
* @var array
*/
protected $configuration = array();
/**
* The CssMinifier of the filter.
*
* @var CssMinifier
*/
protected $minifier = null;
/**
* Constructor.
*
* @param CssMinifier $minifier The CssMinifier object of this plugin.
* @param array $configuration Filter configuration [optional]
* @return void
*/
public function __construct(CssMinifier $minifier, array $configuration = array())
{
$this->configuration = $configuration;
$this->minifier = $minifier;
}
/**
* Filter the tokens.
*
* @param array $tokens Array of objects of type aCssToken
* @return integer Count of added, changed or removed tokens; a return value large than 0 will rebuild the array
*/
abstract public function apply(array &$tokens);
}
/**
* Abstract formatter definition.
*
* Every formatter have to extend this class.
*
* @package CssMin/Formatter
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
abstract class aCssFormatter
{
/**
* Indent string.
*
* @var string
*/
protected $indent = " ";
/**
* Declaration padding.
*
* @var integer
*/
protected $padding = 0;
/**
* Tokens.
*
* @var array
*/
protected $tokens = array();
/**
* Constructor.
*
* @param array $tokens Array of CssToken
* @param string $indent Indent string [optional]
* @param integer $padding Declaration value padding [optional]
*/
public function __construct(array $tokens, $indent = null, $padding = null)
{
$this->tokens = $tokens;
$this->indent = !is_null($indent) ? $indent : $this->indent;
$this->padding = !is_null($padding) ? $padding : $this->padding;
}
/**
* Returns the array of aCssToken as formatted string.
*
* @return string
*/
abstract public function __toString();
}
/**
* Abstract definition of a ruleset declaration token.
*
* @package CssMin/Tokens
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
abstract class aCssDeclarationToken extends aCssToken
{
/**
* Is the declaration flagged as important?
*
* @var boolean
*/
public $IsImportant = false;
/**
* Is the declaration flagged as last one of the ruleset?
*
* @var boolean
*/
public $IsLast = false;
/**
* Property name of the declaration.
*
* @var string
*/
public $Property = "";
/**
* Value of the declaration.
*
* @var string
*/
public $Value = "";
/**
* Set the properties of the @font-face declaration.
*
* @param string $property Property of the declaration
* @param string $value Value of the declaration
* @param boolean $isImportant Is the !important flag is set?
* @param boolean $IsLast Is the declaration the last one of the block?
* @return void
*/
public function __construct($property, $value, $isImportant = false, $isLast = false)
{
$this->Property = $property;
$this->Value = $value;
$this->IsImportant = $isImportant;
$this->IsLast = $isLast;
}
/**
* Implements {@link aCssToken::__toString()}.
*
* @return string
*/
public function __toString()
{
return $this->Property . ":" . $this->Value . ($this->IsImportant ? " !important" : "") . ($this->IsLast ? "" : ";");
}
}
/**
* Abstract definition of a for at-rule block start token.
*
* @package CssMin/Tokens
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
abstract class aCssAtBlockStartToken extends aCssToken
{
}
/**
* Abstract definition of a for at-rule block end token.
*
* @package CssMin/Tokens
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
abstract class aCssAtBlockEndToken extends aCssToken
{
/**
* Implements {@link aCssToken::__toString()}.
*
* @return string
*/
public function __toString()
{
return "}";
}
}
/**
* {@link aCssFromatter Formatter} returning the CSS source in {@link http://goo.gl/etzLs Whitesmiths indent style}.
*
* @package CssMin/Formatter
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssWhitesmithsFormatter extends aCssFormatter
{
/**
* Implements {@link aCssFormatter::__toString()}.
*
* @return string
*/
public function __toString()
{
$r = array();
$level = 0;
for ($i = 0, $l = count($this->tokens); $i < $l; $i++)
{
$token = $this->tokens[$i];
$class = get_class($token);
$indent = str_repeat($this->indent, $level);
if ($class === "CssCommentToken")
{
$lines = array_map("trim", explode("\n", $token->Comment));
for ($ii = 0, $ll = count($lines); $ii < $ll; $ii++)
{
$r[] = $indent . (substr($lines[$ii], 0, 1) == "*" ? " " : "") . $lines[$ii];
}
}
elseif ($class === "CssAtCharsetToken")
{
$r[] = $indent . "@charset " . $token->Charset . ";";
}
elseif ($class === "CssAtFontFaceStartToken")
{
$r[] = $indent . "@font-face";
$r[] = $this->indent . $indent . "{";
$level++;
}
elseif ($class === "CssAtImportToken")
{
$r[] = $indent . "@import " . $token->Import . " " . implode(", ", $token->MediaTypes) . ";";
}
elseif ($class === "CssAtKeyframesStartToken")
{
$r[] = $indent . "@keyframes " . $token->Name;
$r[] = $this->indent . $indent . "{";
$level++;
}
elseif ($class === "CssAtMediaStartToken")
{
$r[] = $indent . "@media " . implode(", ", $token->MediaTypes);
$r[] = $this->indent . $indent . "{";
$level++;
}
elseif ($class === "CssAtPageStartToken")
{
$r[] = $indent . "@page";
$r[] = $this->indent . $indent . "{";
$level++;
}
elseif ($class === "CssAtVariablesStartToken")
{
$r[] = $indent . "@variables " . implode(", ", $token->MediaTypes);
$r[] = $this->indent . $indent . "{";
$level++;
}
elseif ($class === "CssRulesetStartToken" || $class === "CssAtKeyframesRulesetStartToken")
{
$r[] = $indent . implode(", ", $token->Selectors);
$r[] = $this->indent . $indent . "{";
$level++;
}
elseif ($class === "CssAtFontFaceDeclarationToken"
|| $class === "CssAtKeyframesRulesetDeclarationToken"
|| $class === "CssAtPageDeclarationToken"
|| $class === "CssAtVariablesDeclarationToken"
|| $class === "CssRulesetDeclarationToken"
)
{
$declaration = $indent . $token->Property . ": ";
if ($this->padding)
{
$declaration = str_pad($declaration, $this->padding, " ", STR_PAD_RIGHT);
}
$r[] = $declaration . $token->Value . ($token->IsImportant ? " !important" : "") . ";";
}
elseif ($class === "CssAtFontFaceEndToken"
|| $class === "CssAtMediaEndToken"
|| $class === "CssAtKeyframesEndToken"
|| $class === "CssAtKeyframesRulesetEndToken"
|| $class === "CssAtPageEndToken"
|| $class === "CssAtVariablesEndToken"
|| $class === "CssRulesetEndToken"
)
{
$r[] = $indent . "}";
$level--;
}
}
return implode("\n", $r);
}
}
/**
* This {@link aCssMinifierPlugin} will process var-statement and sets the declaration value to the variable value.
*
* This plugin only apply the variable values. The variable values itself will get parsed by the
* {@link CssVariablesMinifierFilter}.
*
* Example:
* <code>
* @variables
* {
* defaultColor: black;
* }
* color: var(defaultColor);
* </code>
*
* Will get converted to:
* <code>
* color:black;
* </code>
*
* @package CssMin/Minifier/Plugins
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssVariablesMinifierPlugin extends aCssMinifierPlugin
{
/**
* Regular expression matching a value.
*
* @var string
*/
private $reMatch = "/var\((.+)\)/iSU";
/**
* Parsed variables.
*
* @var array
*/
private $variables = null;
/**
* Returns the variables.
*
* @return array
*/
public function getVariables()
{
return $this->variables;
}
/**
* Implements {@link aCssMinifierPlugin::minify()}.
*
* @param aCssToken $token Token to process
* @return boolean Return TRUE to break the processing of this token; FALSE to continue
*/
public function apply(aCssToken &$token)
{
if (stripos($token->Value, "var") !== false && preg_match_all($this->reMatch, $token->Value, $m))
{
$mediaTypes = $token->MediaTypes;
if (!in_array("all", $mediaTypes))
{
$mediaTypes[] = "all";
}
for ($i = 0, $l = count($m[0]); $i < $l; $i++)
{
$variable = trim($m[1][$i]);
foreach ($mediaTypes as $mediaType)
{
if (isset($this->variables[$mediaType], $this->variables[$mediaType][$variable]))
{
// Variable value found => set the declaration value to the variable value and return
$token->Value = str_replace($m[0][$i], $this->variables[$mediaType][$variable], $token->Value);
continue 2;
}
}
// If no value was found trigger an error and replace the token with a CssNullToken
CssMin::triggerError(new CssError(__FILE__, __LINE__, __METHOD__ . ": No value found for variable <code>" . $variable . "</code> in media types <code>" . implode(", ", $mediaTypes) . "</code>", (string) $token));
$token = new CssNullToken();
return true;
}
}
return false;
}
/**
* Implements {@link aMinifierPlugin::getTriggerTokens()}
*
* @return array
*/
public function getTriggerTokens()
{
return array
(
"CssAtFontFaceDeclarationToken",
"CssAtPageDeclarationToken",
"CssRulesetDeclarationToken"
);
}
/**
* Sets the variables.
*
* @param array $variables Variables to set
* @return void
*/
public function setVariables(array $variables)
{
$this->variables = $variables;
}
}
/**
* This {@link aCssMinifierFilter minifier filter} will parse the variable declarations out of @variables at-rule
* blocks. The variables will get store in the {@link CssVariablesMinifierPlugin} that will apply the variables to
* declaration.
*
* @package CssMin/Minifier/Filters
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssVariablesMinifierFilter extends aCssMinifierFilter
{
/**
* Implements {@link aCssMinifierFilter::filter()}.
*
* @param array $tokens Array of objects of type aCssToken
* @return integer Count of added, changed or removed tokens; a return value large than 0 will rebuild the array
*/
public function apply(array &$tokens)
{
$variables = array();
$defaultMediaTypes = array("all");
$mediaTypes = array();
$remove = array();
for($i = 0, $l = count($tokens); $i < $l; $i++)
{
// @variables at-rule block found
if (get_class($tokens[$i]) === "CssAtVariablesStartToken")
{
$remove[] = $i;
$mediaTypes = (count($tokens[$i]->MediaTypes) == 0 ? $defaultMediaTypes : $tokens[$i]->MediaTypes);
foreach ($mediaTypes as $mediaType)
{
if (!isset($variables[$mediaType]))
{
$variables[$mediaType] = array();
}
}
// Read the variable declaration tokens
for($i = $i; $i < $l; $i++)
{
// Found a variable declaration => read the variable values
if (get_class($tokens[$i]) === "CssAtVariablesDeclarationToken")
{
foreach ($mediaTypes as $mediaType)
{
$variables[$mediaType][$tokens[$i]->Property] = $tokens[$i]->Value;
}
$remove[] = $i;
}
// Found the variables end token => break;
elseif (get_class($tokens[$i]) === "CssAtVariablesEndToken")
{
$remove[] = $i;
break;
}
}
}
}
// Variables in @variables at-rule blocks
foreach($variables as $mediaType => $null)
{
foreach($variables[$mediaType] as $variable => $value)
{
// If a var() statement in a variable value found...
if (stripos($value, "var") !== false && preg_match_all("/var\((.+)\)/iSU", $value, $m))
{
// ... then replace the var() statement with the variable values.
for ($i = 0, $l = count($m[0]); $i < $l; $i++)
{
$variables[$mediaType][$variable] = str_replace($m[0][$i], (isset($variables[$mediaType][$m[1][$i]]) ? $variables[$mediaType][$m[1][$i]] : ""), $variables[$mediaType][$variable]);
}
}
}
}
// Remove the complete @variables at-rule block
foreach ($remove as $i)
{
$tokens[$i] = null;
}
if (!($plugin = $this->minifier->getPlugin("CssVariablesMinifierPlugin")))
{
CssMin::triggerError(new CssError(__FILE__, __LINE__, __METHOD__ . ": The plugin <code>CssVariablesMinifierPlugin</code> was not found but is required for <code>" . __CLASS__ . "</code>"));
}
else
{
$plugin->setVariables($variables);
}
return count($remove);
}
}
/**
* {@link aCssParserPlugin Parser plugin} for preserve parsing url() values.
*
* This plugin return no {@link aCssToken CssToken} but ensures that url() values will get parsed properly.
*
* @package CssMin/Parser/Plugins
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssUrlParserPlugin extends aCssParserPlugin
{
/**
* Implements {@link aCssParserPlugin::getTriggerChars()}.
*
* @return array
*/
public function getTriggerChars()
{
return array("(", ")");
}
/**
* Implements {@link aCssParserPlugin::getTriggerStates()}.
*
* @return array
*/
public function getTriggerStates()
{
return false;
}
/**
* Implements {@link aCssParserPlugin::parse()}.
*
* @param integer $index Current index
* @param string $char Current char
* @param string $previousChar Previous char
* @return mixed TRUE will break the processing; FALSE continue with the next plugin; integer set a new index and break the processing
*/
public function parse($index, $char, $previousChar, $state)
{
// Start of string
if ($char === "(" && strtolower(substr($this->parser->getSource(), $index - 3, 4)) === "url(" && $state !== "T_URL")
{
$this->parser->pushState("T_URL");
$this->parser->setExclusive(__CLASS__);
}
// Escaped LF in url => remove escape backslash and LF
elseif ($char === "\n" && $previousChar === "\\" && $state === "T_URL")
{
$this->parser->setBuffer(substr($this->parser->getBuffer(), 0, -2));
}
// Parse error: Unescaped LF in string literal
elseif ($char === "\n" && $previousChar !== "\\" && $state === "T_URL")
{
$line = $this->parser->getBuffer();
$this->parser->setBuffer(substr($this->parser->getBuffer(), 0, -1) . ")"); // Replace the LF with the url string delimiter
$this->parser->popState();
$this->parser->unsetExclusive();
CssMin::triggerError(new CssError(__FILE__, __LINE__, __METHOD__ . ": Unterminated string literal", $line . "_"));
}
// End of string
elseif ($char === ")" && $state === "T_URL")
{
$this->parser->popState();
$this->parser->unsetExclusive();
}
else
{
return false;
}
return true;
}
}
/**
* {@link aCssParserPlugin Parser plugin} for preserve parsing string values.
*
* This plugin return no {@link aCssToken CssToken} but ensures that string values will get parsed properly.
*
* @package CssMin/Parser/Plugins
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssStringParserPlugin extends aCssParserPlugin
{
/**
* Current string delimiter char.
*
* @var string
*/
private $delimiterChar = null;
/**
* Implements {@link aCssParserPlugin::getTriggerChars()}.
*
* @return array
*/
public function getTriggerChars()
{
return array("\"", "'", "\n");
}
/**
* Implements {@link aCssParserPlugin::getTriggerStates()}.
*
* @return array
*/
public function getTriggerStates()
{
return false;
}
/**
* Implements {@link aCssParserPlugin::parse()}.
*
* @param integer $index Current index
* @param string $char Current char
* @param string $previousChar Previous char
* @return mixed TRUE will break the processing; FALSE continue with the next plugin; integer set a new index and break the processing
*/
public function parse($index, $char, $previousChar, $state)
{
// Start of string
if (($char === "\"" || $char === "'") && $state !== "T_STRING")
{
$this->delimiterChar = $char;
$this->parser->pushState("T_STRING");
$this->parser->setExclusive(__CLASS__);
}
// Escaped LF in string => remove escape backslash and LF
elseif ($char === "\n" && $previousChar === "\\" && $state === "T_STRING")
{
$this->parser->setBuffer(substr($this->parser->getBuffer(), 0, -2));
}
// Parse error: Unescaped LF in string literal
elseif ($char === "\n" && $previousChar !== "\\" && $state === "T_STRING")
{
$line = $this->parser->getBuffer();
$this->parser->popState();
$this->parser->unsetExclusive();
$this->parser->setBuffer(substr($this->parser->getBuffer(), 0, -1) . $this->delimiterChar); // Replace the LF with the current string char
CssMin::triggerError(new CssError(__FILE__, __LINE__, __METHOD__ . ": Unterminated string literal", $line . "_"));
$this->delimiterChar = null;
}
// End of string
elseif ($char === $this->delimiterChar && $state === "T_STRING")
{
// If the Previous char is a escape char count the amount of the previous escape chars. If the amount of
// escape chars is uneven do not end the string
if ($previousChar == "\\")
{
$source = $this->parser->getSource();
$c = 1;
$i = $index - 2;
while (substr($source, $i, 1) === "\\")
{
$c++; $i--;
}
if ($c % 2)
{
return false;
}
}
$this->parser->popState();
$this->parser->unsetExclusive();
$this->delimiterChar = null;
}
else
{
return false;
}
return true;
}
}
/**
* This {@link aCssMinifierFilter minifier filter} sorts the ruleset declarations of a ruleset by name.
*
* @package CssMin/Minifier/Filters
* @link http://code.google.com/p/cssmin/
* @author Rowan Beentje <http://assanka.net>
* @copyright Rowan Beentje <http://assanka.net>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssSortRulesetPropertiesMinifierFilter extends aCssMinifierFilter
{
/**
* Implements {@link aCssMinifierFilter::filter()}.
*
* @param array $tokens Array of objects of type aCssToken
* @return integer Count of added, changed or removed tokens; a return value larger than 0 will rebuild the array
*/
public function apply(array &$tokens)
{
$r = 0;
for ($i = 0, $l = count($tokens); $i < $l; $i++)
{
// Only look for ruleset start rules
if (get_class($tokens[$i]) !== "CssRulesetStartToken") { continue; }
// Look for the corresponding ruleset end
$endIndex = false;
for ($ii = $i + 1; $ii < $l; $ii++)
{
if (get_class($tokens[$ii]) !== "CssRulesetEndToken") { continue; }
$endIndex = $ii;
break;
}
if (!$endIndex) { break; }
$startIndex = $i;
$i = $endIndex;
// Skip if there's only one token in this ruleset
if ($endIndex - $startIndex <= 2) { continue; }
// Ensure that everything between the start and end is a declaration token, for safety
for ($ii = $startIndex + 1; $ii < $endIndex; $ii++)
{
if (get_class($tokens[$ii]) !== "CssRulesetDeclarationToken") { continue(2); }
}
$declarations = array_slice($tokens, $startIndex + 1, $endIndex - $startIndex - 1);
// Check whether a sort is required
$sortRequired = $lastPropertyName = false;
foreach ($declarations as $declaration)
{
if ($lastPropertyName)
{
if (strcmp($lastPropertyName, $declaration->Property) > 0)
{
$sortRequired = true;
break;
}
}
$lastPropertyName = $declaration->Property;
}
if (!$sortRequired) { continue; }
// Arrange the declarations alphabetically by name
usort($declarations, array(__CLASS__, "userDefinedSort1"));
// Update "IsLast" property
for ($ii = 0, $ll = count($declarations) - 1; $ii <= $ll; $ii++)
{
if ($ii == $ll)
{
$declarations[$ii]->IsLast = true;
}
else
{
$declarations[$ii]->IsLast = false;
}
}
// Splice back into the array.
array_splice($tokens, $startIndex + 1, $endIndex - $startIndex - 1, $declarations);
$r += $endIndex - $startIndex - 1;
}
return $r;
}
/**
* User defined sort function.
*
* @return integer
*/
public static function userDefinedSort1($a, $b)
{
return strcmp($a->Property, $b->Property);
}
}
/**
* This {@link aCssToken CSS token} represents the start of a ruleset.
*
* @package CssMin/Tokens
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssRulesetStartToken extends aCssRulesetStartToken
{
/**
* Array of selectors.
*
* @var array
*/
public $Selectors = array();
/**
* Set the properties of a ruleset token.
*
* @param array $selectors Selectors of the ruleset
* @return void
*/
public function __construct(array $selectors = array())
{
$this->Selectors = $selectors;
}
/**
* Implements {@link aCssToken::__toString()}.
*
* @return string
*/
public function __toString()
{
return implode(",", $this->Selectors) . "{";
}
}
/**
* {@link aCssParserPlugin Parser plugin} for parsing ruleset block with including declarations.
*
* Found rulesets will add a {@link CssRulesetStartToken} and {@link CssRulesetEndToken} to the
* parser; including declarations as {@link CssRulesetDeclarationToken}.
*
* @package CssMin/Parser/Plugins
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssRulesetParserPlugin extends aCssParserPlugin
{
/**
* Implements {@link aCssParserPlugin::getTriggerChars()}.
*
* @return array
*/
public function getTriggerChars()
{
return array(",", "{", "}", ":", ";");
}
/**
* Implements {@link aCssParserPlugin::getTriggerStates()}.
*
* @return array
*/
public function getTriggerStates()
{
return array("T_DOCUMENT", "T_AT_MEDIA", "T_RULESET::SELECTORS", "T_RULESET", "T_RULESET_DECLARATION");
}
/**
* Selectors.
*
* @var array
*/
private $selectors = array();
/**
* Implements {@link aCssParserPlugin::parse()}.
*
* @param integer $index Current index
* @param string $char Current char
* @param string $previousChar Previous char
* @return mixed TRUE will break the processing; FALSE continue with the next plugin; integer set a new index and break the processing
*/
public function parse($index, $char, $previousChar, $state)
{
// Start of Ruleset and selectors
if ($char === "," && ($state === "T_DOCUMENT" || $state === "T_AT_MEDIA" || $state === "T_RULESET::SELECTORS"))
{
if ($state !== "T_RULESET::SELECTORS")
{
$this->parser->pushState("T_RULESET::SELECTORS");
}
$this->selectors[] = $this->parser->getAndClearBuffer(",{");
}
// End of selectors and start of declarations
elseif ($char === "{" && ($state === "T_DOCUMENT" || $state === "T_AT_MEDIA" || $state === "T_RULESET::SELECTORS"))
{
if ($this->parser->getBuffer() !== "")
{
$this->selectors[] = $this->parser->getAndClearBuffer(",{");
if ($state == "T_RULESET::SELECTORS")
{
$this->parser->popState();
}
$this->parser->pushState("T_RULESET");
$this->parser->appendToken(new CssRulesetStartToken($this->selectors));
$this->selectors = array();
}
}
// Start of declaration
elseif ($char === ":" && $state === "T_RULESET")
{
$this->parser->pushState("T_RULESET_DECLARATION");
$this->buffer = $this->parser->getAndClearBuffer(":;", true);
}
// Unterminated ruleset declaration
elseif ($char === ":" && $state === "T_RULESET_DECLARATION")
{
// Ignore Internet Explorer filter declarations
if ($this->buffer === "filter")
{
return false;
}
CssMin::triggerError(new CssError(__FILE__, __LINE__, __METHOD__ . ": Unterminated declaration", $this->buffer . ":" . $this->parser->getBuffer() . "_"));
}
// End of declaration
elseif (($char === ";" || $char === "}") && $state === "T_RULESET_DECLARATION")
{
$value = $this->parser->getAndClearBuffer(";}");
if (strtolower(substr($value, -10, 10)) === "!important")
{
$value = trim(substr($value, 0, -10));
$isImportant = true;
}
else
{
$isImportant = false;
}
$this->parser->popState();
$this->parser->appendToken(new CssRulesetDeclarationToken($this->buffer, $value, $this->parser->getMediaTypes(), $isImportant));
// Declaration ends with a right curly brace; so we have to end the ruleset
if ($char === "}")
{
$this->parser->appendToken(new CssRulesetEndToken());
$this->parser->popState();
}
$this->buffer = "";
}
// End of ruleset
elseif ($char === "}" && $state === "T_RULESET")
{
$this->parser->popState();
$this->parser->clearBuffer();
$this->parser->appendToken(new CssRulesetEndToken());
$this->buffer = "";
$this->selectors = array();
}
else
{
return false;
}
return true;
}
}
/**
* This {@link aCssToken CSS token} represents the end of a ruleset.
*
* @package CssMin/Tokens
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssRulesetEndToken extends aCssRulesetEndToken
{
}
/**
* This {@link aCssToken CSS token} represents a ruleset declaration.
*
* @package CssMin/Tokens
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssRulesetDeclarationToken extends aCssDeclarationToken
{
/**
* Media types of the declaration.
*
* @var array
*/
public $MediaTypes = array("all");
/**
* Set the properties of a ddocument- or at-rule @media level declaration.
*
* @param string $property Property of the declaration
* @param string $value Value of the declaration
* @param mixed $mediaTypes Media types of the declaration
* @param boolean $isImportant Is the !important flag is set
* @param boolean $isLast Is the declaration the last one of the ruleset
* @return void
*/
public function __construct($property, $value, $mediaTypes = null, $isImportant = false, $isLast = false)
{
parent::__construct($property, $value, $isImportant, $isLast);
$this->MediaTypes = $mediaTypes ? $mediaTypes : array("all");
}
}
/**
* This {@link aCssMinifierFilter minifier filter} sets the IsLast property of any last declaration in a ruleset,
* @font-face at-rule or @page at-rule block. If the property IsLast is TRUE the decrations will get stringified
* without tailing semicolon.
*
* @package CssMin/Minifier/Filters
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssRemoveLastDelarationSemiColonMinifierFilter extends aCssMinifierFilter
{
/**
* Implements {@link aCssMinifierFilter::filter()}.
*
* @param array $tokens Array of objects of type aCssToken
* @return integer Count of added, changed or removed tokens; a return value large than 0 will rebuild the array
*/
public function apply(array &$tokens)
{
for ($i = 0, $l = count($tokens); $i < $l; $i++)
{
$current = get_class($tokens[$i]);
$next = isset($tokens[$i+1]) ? get_class($tokens[$i+1]) : false;
if (($current === "CssRulesetDeclarationToken" && $next === "CssRulesetEndToken") ||
($current === "CssAtFontFaceDeclarationToken" && $next === "CssAtFontFaceEndToken") ||
($current === "CssAtPageDeclarationToken" && $next === "CssAtPageEndToken"))
{
$tokens[$i]->IsLast = true;
}
}
return 0;
}
}
/**
* This {@link aCssMinifierFilter minifier filter} will remove any empty rulesets (including @keyframes at-rule block
* rulesets).
*
* @package CssMin/Minifier/Filters
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssRemoveEmptyRulesetsMinifierFilter extends aCssMinifierFilter
{
/**
* Implements {@link aCssMinifierFilter::filter()}.
*
* @param array $tokens Array of objects of type aCssToken
* @return integer Count of added, changed or removed tokens; a return value large than 0 will rebuild the array
*/
public function apply(array &$tokens)
{
$r = 0;
for ($i = 0, $l = count($tokens); $i < $l; $i++)
{
$current = get_class($tokens[$i]);
$next = isset($tokens[$i + 1]) ? get_class($tokens[$i + 1]) : false;
if (($current === "CssRulesetStartToken" && $next === "CssRulesetEndToken") ||
($current === "CssAtKeyframesRulesetStartToken" && $next === "CssAtKeyframesRulesetEndToken" && !array_intersect(array("from", "0%", "to", "100%"), array_map("strtolower", $tokens[$i]->Selectors)))
)
{
$tokens[$i] = null;
$tokens[$i + 1] = null;
$i++;
$r = $r + 2;
}
}
return $r;
}
}
/**
* This {@link aCssMinifierFilter minifier filter} will remove any empty @font-face, @keyframes, @media and @page
* at-rule blocks.
*
* @package CssMin/Minifier/Filters
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssRemoveEmptyAtBlocksMinifierFilter extends aCssMinifierFilter
{
/**
* Implements {@link aCssMinifierFilter::filter()}.
*
* @param array $tokens Array of objects of type aCssToken
* @return integer Count of added, changed or removed tokens; a return value large than 0 will rebuild the array
*/
public function apply(array &$tokens)
{
$r = 0;
for ($i = 0, $l = count($tokens); $i < $l; $i++)
{
$current = get_class($tokens[$i]);
$next = isset($tokens[$i + 1]) ? get_class($tokens[$i + 1]) : false;
if (($current === "CssAtFontFaceStartToken" && $next === "CssAtFontFaceEndToken") ||
($current === "CssAtKeyframesStartToken" && $next === "CssAtKeyframesEndToken") ||
($current === "CssAtPageStartToken" && $next === "CssAtPageEndToken") ||
($current === "CssAtMediaStartToken" && $next === "CssAtMediaEndToken"))
{
$tokens[$i] = null;
$tokens[$i + 1] = null;
$i++;
$r = $r + 2;
}
}
return $r;
}
}
/**
* This {@link aCssMinifierFilter minifier filter} will remove any comments from the array of parsed tokens.
*
* @package CssMin/Minifier/Filters
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssRemoveCommentsMinifierFilter extends aCssMinifierFilter
{
/**
* Implements {@link aCssMinifierFilter::filter()}.
*
* @param array $tokens Array of objects of type aCssToken
* @return integer Count of added, changed or removed tokens; a return value large than 0 will rebuild the array
*/
public function apply(array &$tokens)
{
$r = 0;
for ($i = 0, $l = count($tokens); $i < $l; $i++)
{
if (get_class($tokens[$i]) === "CssCommentToken")
{
$tokens[$i] = null;
$r++;
}
}
return $r;
}
}
/**
* CSS Parser.
*
* @package CssMin/Parser
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssParser
{
/**
* Parse buffer.
*
* @var string
*/
private $buffer = "";
/**
* {@link aCssParserPlugin Plugins}.
*
* @var array
*/
private $plugins = array();
/**
* Source to parse.
*
* @var string
*/
private $source = "";
/**
* Current state.
*
* @var integer
*/
private $state = "T_DOCUMENT";
/**
* Exclusive state.
*
* @var string
*/
private $stateExclusive = false;
/**
* Media types state.
*
* @var mixed
*/
private $stateMediaTypes = false;
/**
* State stack.
*
* @var array
*/
private $states = array("T_DOCUMENT");
/**
* Parsed tokens.
*
* @var array
*/
private $tokens = array();
/**
* Constructer.
*
* Create instances of the used {@link aCssParserPlugin plugins}.
*
* @param string $source CSS source [optional]
* @param array $plugins Plugin configuration [optional]
* @return void
*/
public function __construct($source = null, array $plugins = null)
{
$plugins = array_merge(array
(
"Comment" => true,
"String" => true,
"Url" => true,
"Expression" => true,
"Ruleset" => true,
"AtCharset" => true,
"AtFontFace" => true,
"AtImport" => true,
"AtKeyframes" => true,
"AtMedia" => true,
"AtPage" => true,
"AtVariables" => true
), is_array($plugins) ? $plugins : array());
// Create plugin instances
foreach ($plugins as $name => $config)
{
if ($config !== false)
{
$class = "Css" . $name . "ParserPlugin";
$config = is_array($config) ? $config : array();
if (class_exists($class))
{
$this->plugins[] = new $class($this, $config);
}
else
{
CssMin::triggerError(new CssError(__FILE__, __LINE__, __METHOD__ . ": The plugin <code>" . $name . "</code> with the class name <code>" . $class . "</code> was not found"));
}
}
}
if (!is_null($source))
{
$this->parse($source);
}
}
/**
* Append a token to the array of tokens.
*
* @param aCssToken $token Token to append
* @return void
*/
public function appendToken(aCssToken $token)
{
$this->tokens[] = $token;
}
/**
* Clears the current buffer.
*
* @return void
*/
public function clearBuffer()
{
$this->buffer = "";
}
/**
* Returns and clear the current buffer.
*
* @param string $trim Chars to use to trim the returned buffer
* @param boolean $tolower if TRUE the returned buffer will get converted to lower case
* @return string
*/
public function getAndClearBuffer($trim = "", $tolower = false)
{
$r = $this->getBuffer($trim, $tolower);
$this->buffer = "";
return $r;
}
/**
* Returns the current buffer.
*
* @param string $trim Chars to use to trim the returned buffer
* @param boolean $tolower if TRUE the returned buffer will get converted to lower case
* @return string
*/
public function getBuffer($trim = "", $tolower = false)
{
$r = $this->buffer;
if ($trim)
{
$r = trim($r, " \t\n\r\0\x0B" . $trim);
}
if ($tolower)
{
$r = strtolower($r);
}
return $r;
}
/**
* Returns the current media types state.
*
* @return array
*/
public function getMediaTypes()
{
return $this->stateMediaTypes;
}
/**
* Returns the CSS source.
*
* @return string
*/
public function getSource()
{
return $this->source;
}
/**
* Returns the current state.
*
* @return integer The current state
*/
public function getState()
{
return $this->state;
}
/**
* Returns a plugin by class name.
*
* @param string $name Class name of the plugin
* @return aCssParserPlugin
*/
public function getPlugin($class)
{
static $index = null;
if (is_null($index))
{
$index = array();
for ($i = 0, $l = count($this->plugins); $i < $l; $i++)
{
$index[get_class($this->plugins[$i])] = $i;
}
}
return isset($index[$class]) ? $this->plugins[$index[$class]] : false;
}
/**
* Returns the parsed tokens.
*
* @return array
*/
public function getTokens()
{
return $this->tokens;
}
/**
* Returns if the current state equals the passed state.
*
* @param integer $state State to compare with the current state
* @return boolean TRUE is the state equals to the passed state; FALSE if not
*/
public function isState($state)
{
return ($this->state == $state);
}
/**
* Parse the CSS source and return a array with parsed tokens.
*
* @param string $source CSS source
* @return array Array with tokens
*/
public function parse($source)
{
// Reset
$this->source = "";
$this->tokens = array();
// Create a global and plugin lookup table for trigger chars; set array of plugins as local variable and create
// several helper variables for plugin handling
$globalTriggerChars = "";
$plugins = $this->plugins;
$pluginCount = count($plugins);
$pluginIndex = array();
$pluginTriggerStates = array();
$pluginTriggerChars = array();
for ($i = 0, $l = count($plugins); $i < $l; $i++)
{
$tPluginClassName = get_class($plugins[$i]);
$pluginTriggerChars[$i] = implode("", $plugins[$i]->getTriggerChars());
$tPluginTriggerStates = $plugins[$i]->getTriggerStates();
$pluginTriggerStates[$i] = $tPluginTriggerStates === false ? false : "|" . implode("|", $tPluginTriggerStates) . "|";
$pluginIndex[$tPluginClassName] = $i;
for ($ii = 0, $ll = strlen($pluginTriggerChars[$i]); $ii < $ll; $ii++)
{
$c = substr($pluginTriggerChars[$i], $ii, 1);
if (strpos($globalTriggerChars, $c) === false)
{
$globalTriggerChars .= $c;
}
}
}
// Normalise line endings
$source = str_replace("\r\n", "\n", $source); // Windows to Unix line endings
$source = str_replace("\r", "\n", $source); // Mac to Unix line endings
$this->source = $source;
// Variables
$buffer = &$this->buffer;
$exclusive = &$this->stateExclusive;
$state = &$this->state;
$c = $p = null;
// --
for ($i = 0, $l = strlen($source); $i < $l; $i++)
{
// Set the current Char
$c = $source[$i]; // Is faster than: $c = substr($source, $i, 1);
// Normalize and filter double whitespace characters
if ($exclusive === false)
{
if ($c === "\n" || $c === "\t")
{
$c = " ";
}
if ($c === " " && $p === " ")
{
continue;
}
}
$buffer .= $c;
// Extended processing only if the current char is a global trigger char
if (strpos($globalTriggerChars, $c) !== false)
{
// Exclusive state is set; process with the exclusive plugin
if ($exclusive)
{
$tPluginIndex = $pluginIndex[$exclusive];
if (strpos($pluginTriggerChars[$tPluginIndex], $c) !== false && ($pluginTriggerStates[$tPluginIndex] === false || strpos($pluginTriggerStates[$tPluginIndex], $state) !== false))
{
$r = $plugins[$tPluginIndex]->parse($i, $c, $p, $state);
// Return value is TRUE => continue with next char
if ($r === true)
{
continue;
}
// Return value is numeric => set new index and continue with next char
elseif ($r !== false && $r != $i)
{
$i = $r;
continue;
}
}
}
// Else iterate through the plugins
else
{
$triggerState = "|" . $state . "|";
for ($ii = 0, $ll = $pluginCount; $ii < $ll; $ii++)
{
// Only process if the current char is one of the plugin trigger chars
if (strpos($pluginTriggerChars[$ii], $c) !== false && ($pluginTriggerStates[$ii] === false || strpos($pluginTriggerStates[$ii], $triggerState) !== false))
{
// Process with the plugin
$r = $plugins[$ii]->parse($i, $c, $p, $state);
// Return value is TRUE => break the plugin loop and and continue with next char
if ($r === true)
{
break;
}
// Return value is numeric => set new index, break the plugin loop and and continue with next char
elseif ($r !== false && $r != $i)
{
$i = $r;
break;
}
}
}
}
}
$p = $c; // Set the parent char
}
return $this->tokens;
}
/**
* Remove the last state of the state stack and return the removed stack value.
*
* @return integer Removed state value
*/
public function popState()
{
$r = array_pop($this->states);
$this->state = $this->states[count($this->states) - 1];
return $r;
}
/**
* Adds a new state onto the state stack.
*
* @param integer $state State to add onto the state stack.
* @return integer The index of the added state in the state stacks
*/
public function pushState($state)
{
$r = array_push($this->states, $state);
$this->state = $this->states[count($this->states) - 1];
return $r;
}
/**
* Sets/restores the buffer.
*
* @param string $buffer Buffer to set
* @return void
*/
public function setBuffer($buffer)
{
$this->buffer = $buffer;
}
/**
* Set the exclusive state.
*
* @param string $exclusive Exclusive state
* @return void
*/
public function setExclusive($exclusive)
{
$this->stateExclusive = $exclusive;
}
/**
* Set the media types state.
*
* @param array $mediaTypes Media types state
* @return void
*/
public function setMediaTypes(array $mediaTypes)
{
$this->stateMediaTypes = $mediaTypes;
}
/**
* Sets the current state in the state stack; equals to {@link CssParser::popState()} + {@link CssParser::pushState()}.
*
* @param integer $state State to set
* @return integer
*/
public function setState($state)
{
$r = array_pop($this->states);
array_push($this->states, $state);
$this->state = $this->states[count($this->states) - 1];
return $r;
}
/**
* Removes the exclusive state.
*
* @return void
*/
public function unsetExclusive()
{
$this->stateExclusive = false;
}
/**
* Removes the media types state.
*
* @return void
*/
public function unsetMediaTypes()
{
$this->stateMediaTypes = false;
}
}
/**
* {@link aCssFromatter Formatter} returning the CSS source in {@link http://goo.gl/j4XdU OTBS indent style} (The One True Brace Style).
*
* @package CssMin/Formatter
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssOtbsFormatter extends aCssFormatter
{
/**
* Implements {@link aCssFormatter::__toString()}.
*
* @return string
*/
public function __toString()
{
$r = array();
$level = 0;
for ($i = 0, $l = count($this->tokens); $i < $l; $i++)
{
$token = $this->tokens[$i];
$class = get_class($token);
$indent = str_repeat($this->indent, $level);
if ($class === "CssCommentToken")
{
$lines = array_map("trim", explode("\n", $token->Comment));
for ($ii = 0, $ll = count($lines); $ii < $ll; $ii++)
{
$r[] = $indent . (substr($lines[$ii], 0, 1) == "*" ? " " : "") . $lines[$ii];
}
}
elseif ($class === "CssAtCharsetToken")
{
$r[] = $indent . "@charset " . $token->Charset . ";";
}
elseif ($class === "CssAtFontFaceStartToken")
{
$r[] = $indent . "@font-face {";
$level++;
}
elseif ($class === "CssAtImportToken")
{
$r[] = $indent . "@import " . $token->Import . " " . implode(", ", $token->MediaTypes) . ";";
}
elseif ($class === "CssAtKeyframesStartToken")
{
$r[] = $indent . "@keyframes " . $token->Name . " {";
$level++;
}
elseif ($class === "CssAtMediaStartToken")
{
$r[] = $indent . "@media " . implode(", ", $token->MediaTypes) . " {";
$level++;
}
elseif ($class === "CssAtPageStartToken")
{
$r[] = $indent . "@page {";
$level++;
}
elseif ($class === "CssAtVariablesStartToken")
{
$r[] = $indent . "@variables " . implode(", ", $token->MediaTypes) . " {";
$level++;
}
elseif ($class === "CssRulesetStartToken" || $class === "CssAtKeyframesRulesetStartToken")
{
$r[] = $indent . implode(", ", $token->Selectors) . " {";
$level++;
}
elseif ($class === "CssAtFontFaceDeclarationToken"
|| $class === "CssAtKeyframesRulesetDeclarationToken"
|| $class === "CssAtPageDeclarationToken"
|| $class === "CssAtVariablesDeclarationToken"
|| $class === "CssRulesetDeclarationToken"
)
{
$declaration = $indent . $token->Property . ": ";
if ($this->padding)
{
$declaration = str_pad($declaration, $this->padding, " ", STR_PAD_RIGHT);
}
$r[] = $declaration . $token->Value . ($token->IsImportant ? " !important" : "") . ";";
}
elseif ($class === "CssAtFontFaceEndToken"
|| $class === "CssAtMediaEndToken"
|| $class === "CssAtKeyframesEndToken"
|| $class === "CssAtKeyframesRulesetEndToken"
|| $class === "CssAtPageEndToken"
|| $class === "CssAtVariablesEndToken"
|| $class === "CssRulesetEndToken"
)
{
$level--;
$r[] = str_repeat($indent, $level) . "}";
}
}
return implode("\n", $r);
}
}
/**
* This {@link aCssToken CSS token} is a utility token that extends {@link aNullToken} and returns only a empty string.
*
* @package CssMin/Tokens
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssNullToken extends aCssToken
{
/**
* Implements {@link aCssToken::__toString()}.
*
* @return string
*/
public function __toString()
{
return "";
}
}
/**
* CSS Minifier.
*
* @package CssMin/Minifier
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssMinifier
{
/**
* {@link aCssMinifierFilter Filters}.
*
* @var array
*/
private $filters = array();
/**
* {@link aCssMinifierPlugin Plugins}.
*
* @var array
*/
private $plugins = array();
/**
* Minified source.
*
* @var string
*/
private $minified = "";
/**
* Constructer.
*
* Creates instances of {@link aCssMinifierFilter filters} and {@link aCssMinifierPlugin plugins}.
*
* @param string $source CSS source [optional]
* @param array $filters Filter configuration [optional]
* @param array $plugins Plugin configuration [optional]
* @return void
*/
public function __construct($source = null, array $filters = null, array $plugins = null)
{
$filters = array_merge(array
(
"ImportImports" => false,
"RemoveComments" => true,
"RemoveEmptyRulesets" => true,
"RemoveEmptyAtBlocks" => true,
"ConvertLevel3Properties" => false,
"ConvertLevel3AtKeyframes" => false,
"Variables" => true,
"RemoveLastDelarationSemiColon" => true
), is_array($filters) ? $filters : array());
$plugins = array_merge(array
(
"Variables" => true,
"ConvertFontWeight" => false,
"ConvertHslColors" => false,
"ConvertRgbColors" => false,
"ConvertNamedColors" => false,
"CompressColorValues" => false,
"CompressUnitValues" => false,
"CompressExpressionValues" => false
), is_array($plugins) ? $plugins : array());
// Filters
foreach ($filters as $name => $config)
{
if ($config !== false)
{
$class = "Css" . $name . "MinifierFilter";
$config = is_array($config) ? $config : array();
if (class_exists($class))
{
$this->filters[] = new $class($this, $config);
}
else
{
CssMin::triggerError(new CssError(__FILE__, __LINE__, __METHOD__ . ": The filter <code>" . $name . "</code> with the class name <code>" . $class . "</code> was not found"));
}
}
}
// Plugins
foreach ($plugins as $name => $config)
{
if ($config !== false)
{
$class = "Css" . $name . "MinifierPlugin";
$config = is_array($config) ? $config : array();
if (class_exists($class))
{
$this->plugins[] = new $class($this, $config);
}
else
{
CssMin::triggerError(new CssError(__FILE__, __LINE__, __METHOD__ . ": The plugin <code>" . $name . "</code> with the class name <code>" . $class . "</code> was not found"));
}
}
}
// --
if (!is_null($source))
{
$this->minify($source);
}
}
/**
* Returns the minified Source.
*
* @return string
*/
public function getMinified()
{
return $this->minified;
}
/**
* Returns a plugin by class name.
*
* @param string $name Class name of the plugin
* @return aCssMinifierPlugin
*/
public function getPlugin($class)
{
static $index = null;
if (is_null($index))
{
$index = array();
for ($i = 0, $l = count($this->plugins); $i < $l; $i++)
{
$index[get_class($this->plugins[$i])] = $i;
}
}
return isset($index[$class]) ? $this->plugins[$index[$class]] : false;
}
/**
* Minifies the CSS source.
*
* @param string $source CSS source
* @return string
*/
public function minify($source)
{
// Variables
$r = "";
$parser = new CssParser($source);
$tokens = $parser->getTokens();
$filters = $this->filters;
$filterCount = count($this->filters);
$plugins = $this->plugins;
$pluginCount = count($plugins);
$pluginIndex = array();
$pluginTriggerTokens = array();
$globalTriggerTokens = array();
for ($i = 0, $l = count($plugins); $i < $l; $i++)
{
$tPluginClassName = get_class($plugins[$i]);
$pluginTriggerTokens[$i] = $plugins[$i]->getTriggerTokens();
foreach ($pluginTriggerTokens[$i] as $v)
{
if (!in_array($v, $globalTriggerTokens))
{
$globalTriggerTokens[] = $v;
}
}
$pluginTriggerTokens[$i] = "|" . implode("|", $pluginTriggerTokens[$i]) . "|";
$pluginIndex[$tPluginClassName] = $i;
}
$globalTriggerTokens = "|" . implode("|", $globalTriggerTokens) . "|";
/*
* Apply filters
*/
for($i = 0; $i < $filterCount; $i++)
{
// Apply the filter; if the return value is larger than 0...
if ($filters[$i]->apply($tokens) > 0)
{
// ...then filter null values and rebuild the token array
$tokens = array_values(array_filter($tokens));
}
}
$tokenCount = count($tokens);
/*
* Apply plugins
*/
for($i = 0; $i < $tokenCount; $i++)
{
$triggerToken = "|" . get_class($tokens[$i]) . "|";
if (strpos($globalTriggerTokens, $triggerToken) !== false)
{
for($ii = 0; $ii < $pluginCount; $ii++)
{
if (strpos($pluginTriggerTokens[$ii], $triggerToken) !== false || $pluginTriggerTokens[$ii] === false)
{
// Apply the plugin; if the return value is TRUE continue to the next token
if ($plugins[$ii]->apply($tokens[$i]) === true)
{
continue 2;
}
}
}
}
}
// Stringify the tokens
for($i = 0; $i < $tokenCount; $i++)
{
$r .= (string) $tokens[$i];
}
$this->minified = $r;
return $r;
}
}
/**
* CssMin - A (simple) css minifier with benefits
*
* --
* Copyright (c) 2011 Joe Scylla <joe.scylla@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
* --
*
* @package CssMin
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssMin
{
/**
* Index of classes
*
* @var array
*/
private static $classIndex = array();
/**
* Parse/minify errors
*
* @var array
*/
private static $errors = array();
/**
* Verbose output.
*
* @var boolean
*/
private static $isVerbose = false;
/**
* {@link http://goo.gl/JrW54 Autoload} function of CssMin.
*
* @param string $class Name of the class
* @return void
*/
public static function autoload($class)
{
if (isset(self::$classIndex[$class]))
{
require(self::$classIndex[$class]);
}
}
/**
* Return errors
*
* @return array of {CssError}.
*/
public static function getErrors()
{
return self::$errors;
}
/**
* Returns if there were errors.
*
* @return boolean
*/
public static function hasErrors()
{
return count(self::$errors) > 0;
}
/**
* Initialises CssMin.
*
* @return void
*/
public static function initialise()
{
// Create the class index for autoloading or including
$paths = array(dirname(__FILE__));
while (list($i, $path) = each($paths))
{
$subDirectorys = glob($path . "*", GLOB_MARK | GLOB_ONLYDIR | GLOB_NOSORT);
if (is_array($subDirectorys))
{
foreach ($subDirectorys as $subDirectory)
{
$paths[] = $subDirectory;
}
}
$files = glob($path . "*.php", 0);
if (is_array($files))
{
foreach ($files as $file)
{
$class = substr(basename($file), 0, -4);
self::$classIndex[$class] = $file;
}
}
}
krsort(self::$classIndex);
// Only use autoloading if spl_autoload_register() is available and no __autoload() is defined (because
// __autoload() breaks if spl_autoload_register() is used.
if (function_exists("spl_autoload_register") && !is_callable("__autoload"))
{
spl_autoload_register(array(__CLASS__, "autoload"));
}
// Otherwise include all class files
else
{
foreach (self::$classIndex as $class => $file)
{
if (!class_exists($class))
{
require_once($file);
}
}
}
}
/**
* Minifies CSS source.
*
* @param string $source CSS source
* @param array $filters Filter configuration [optional]
* @param array $plugins Plugin configuration [optional]
* @return string Minified CSS
*/
public static function minify($source, array $filters = null, array $plugins = null)
{
self::$errors = array();
$minifier = new CssMinifier($source, $filters, $plugins);
return $minifier->getMinified();
}
/**
* Parse the CSS source.
*
* @param string $source CSS source
* @param array $plugins Plugin configuration [optional]
* @return array Array of aCssToken
*/
public static function parse($source, array $plugins = null)
{
self::$errors = array();
$parser = new CssParser($source, $plugins);
return $parser->getTokens();
}
/**
* --
*
* @param boolean $to
* @return boolean
*/
public static function setVerbose($to)
{
self::$isVerbose = (boolean) $to;
return self::$isVerbose;
}
/**
* --
*
* @param CssError $error
* @return void
*/
public static function triggerError(CssError $error)
{
self::$errors[] = $error;
if (self::$isVerbose)
{
trigger_error((string) $error, E_USER_WARNING);
}
}
}
// Initialises CssMin
CssMin::initialise();
/**
* This {@link aCssMinifierFilter minifier filter} import external css files defined with the @import at-rule into the
* current stylesheet.
*
* @package CssMin/Minifier/Filters
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssImportImportsMinifierFilter extends aCssMinifierFilter
{
/**
* Array with already imported external stylesheets.
*
* @var array
*/
private $imported = array();
/**
* Implements {@link aCssMinifierFilter::filter()}.
*
* @param array $tokens Array of objects of type aCssToken
* @return integer Count of added, changed or removed tokens; a return value large than 0 will rebuild the array
*/
public function apply(array &$tokens)
{
if (!isset($this->configuration["BasePath"]) || !is_dir($this->configuration["BasePath"]))
{
CssMin::triggerError(new CssError(__FILE__, __LINE__, __METHOD__ . ": Base path <code>" . ($this->configuration["BasePath"] ? $this->configuration["BasePath"] : "null"). "</code> is not a directory"));
return 0;
}
for ($i = 0, $l = count($tokens); $i < $l; $i++)
{
if (get_class($tokens[$i]) === "CssAtImportToken")
{
$import = $this->configuration["BasePath"] . "/" . $tokens[$i]->Import;
// Import file was not found/is not a file
if (!is_file($import))
{
CssMin::triggerError(new CssError(__FILE__, __LINE__, __METHOD__ . ": Import file <code>" . $import. "</code> was not found.", (string) $tokens[$i]));
}
// Import file already imported; remove this @import at-rule to prevent recursions
elseif (in_array($import, $this->imported))
{
CssMin::triggerError(new CssError(__FILE__, __LINE__, __METHOD__ . ": Import file <code>" . $import. "</code> was already imported.", (string) $tokens[$i]));
$tokens[$i] = null;
}
else
{
$this->imported[] = $import;
$parser = new CssParser(file_get_contents($import));
$import = $parser->getTokens();
// The @import at-rule has media types defined requiring special handling
if (count($tokens[$i]->MediaTypes) > 0 && !(count($tokens[$i]->MediaTypes) == 1 && $tokens[$i]->MediaTypes[0] == "all"))
{
$blocks = array();
/*
* Filter or set media types of @import at-rule or remove the @import at-rule if no media type is matching the parent @import at-rule
*/
for($ii = 0, $ll = count($import); $ii < $ll; $ii++)
{
if (get_class($import[$ii]) === "CssAtImportToken")
{
// @import at-rule defines no media type or only the "all" media type; set the media types to the one defined in the parent @import at-rule
if (count($import[$ii]->MediaTypes) == 0 || (count($import[$ii]->MediaTypes) == 1 && $import[$ii]->MediaTypes[0] == "all"))
{
$import[$ii]->MediaTypes = $tokens[$i]->MediaTypes;
}
// @import at-rule defineds one or more media types; filter out media types not matching with the parent @import at-rule
elseif (count($import[$ii]->MediaTypes > 0))
{
foreach ($import[$ii]->MediaTypes as $index => $mediaType)
{
if (!in_array($mediaType, $tokens[$i]->MediaTypes))
{
unset($import[$ii]->MediaTypes[$index]);
}
}
$import[$ii]->MediaTypes = array_values($import[$ii]->MediaTypes);
// If there are no media types left in the @import at-rule remove the @import at-rule
if (count($import[$ii]->MediaTypes) == 0)
{
$import[$ii] = null;
}
}
}
}
/*
* Remove media types of @media at-rule block not defined in the @import at-rule
*/
for($ii = 0, $ll = count($import); $ii < $ll; $ii++)
{
if (get_class($import[$ii]) === "CssAtMediaStartToken")
{
foreach ($import[$ii]->MediaTypes as $index => $mediaType)
{
if (!in_array($mediaType, $tokens[$i]->MediaTypes))
{
unset($import[$ii]->MediaTypes[$index]);
}
$import[$ii]->MediaTypes = array_values($import[$ii]->MediaTypes);
}
}
}
/*
* If no media types left of the @media at-rule block remove the complete block
*/
for($ii = 0, $ll = count($import); $ii < $ll; $ii++)
{
if (get_class($import[$ii]) === "CssAtMediaStartToken")
{
if (count($import[$ii]->MediaTypes) === 0)
{
for ($iii = $ii; $iii < $ll; $iii++)
{
if (get_class($import[$iii]) === "CssAtMediaEndToken")
{
break;
}
}
if (get_class($import[$iii]) === "CssAtMediaEndToken")
{
array_splice($import, $ii, $iii - $ii + 1, array());
$ll = count($import);
}
}
}
}
/*
* If the media types of the @media at-rule equals the media types defined in the @import
* at-rule remove the CssAtMediaStartToken and CssAtMediaEndToken token
*/
for($ii = 0, $ll = count($import); $ii < $ll; $ii++)
{
if (get_class($import[$ii]) === "CssAtMediaStartToken" && count(array_diff($tokens[$i]->MediaTypes, $import[$ii]->MediaTypes)) === 0)
{
for ($iii = $ii; $iii < $ll; $iii++)
{
if (get_class($import[$iii]) == "CssAtMediaEndToken")
{
break;
}
}
if (get_class($import[$iii]) == "CssAtMediaEndToken")
{
unset($import[$ii]);
unset($import[$iii]);
$import = array_values($import);
$ll = count($import);
}
}
}
/**
* Extract CssAtImportToken and CssAtCharsetToken tokens
*/
for($ii = 0, $ll = count($import); $ii < $ll; $ii++)
{
$class = get_class($import[$ii]);
if ($class === "CssAtImportToken" || $class === "CssAtCharsetToken")
{
$blocks = array_merge($blocks, array_splice($import, $ii, 1, array()));
$ll = count($import);
}
}
/*
* Extract the @font-face, @media and @page at-rule block
*/
for($ii = 0, $ll = count($import); $ii < $ll; $ii++)
{
$class = get_class($import[$ii]);
if ($class === "CssAtFontFaceStartToken" || $class === "CssAtMediaStartToken" || $class === "CssAtPageStartToken" || $class === "CssAtVariablesStartToken")
{
for ($iii = $ii; $iii < $ll; $iii++)
{
$class = get_class($import[$iii]);
if ($class === "CssAtFontFaceEndToken" || $class === "CssAtMediaEndToken" || $class === "CssAtPageEndToken" || $class === "CssAtVariablesEndToken")
{
break;
}
}
$class = get_class($import[$iii]);
if (isset($import[$iii]) && ($class === "CssAtFontFaceEndToken" || $class === "CssAtMediaEndToken" || $class === "CssAtPageEndToken" || $class === "CssAtVariablesEndToken"))
{
$blocks = array_merge($blocks, array_splice($import, $ii, $iii - $ii + 1, array()));
$ll = count($import);
}
}
}
// Create the import array with extracted tokens and the rulesets wrapped into a @media at-rule block
$import = array_merge($blocks, array(new CssAtMediaStartToken($tokens[$i]->MediaTypes)), $import, array(new CssAtMediaEndToken()));
}
// Insert the imported tokens
array_splice($tokens, $i, 1, $import);
// Modify parameters of the for-loop
$i--;
$l = count($tokens);
}
}
}
}
}
/**
* {@link aCssParserPlugin Parser plugin} for preserve parsing expression() declaration values.
*
* This plugin return no {@link aCssToken CssToken} but ensures that expression() declaration values will get parsed
* properly.
*
* @package CssMin/Parser/Plugins
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssExpressionParserPlugin extends aCssParserPlugin
{
/**
* Count of left braces.
*
* @var integer
*/
private $leftBraces = 0;
/**
* Count of right braces.
*
* @var integer
*/
private $rightBraces = 0;
/**
* Implements {@link aCssParserPlugin::getTriggerChars()}.
*
* @return array
*/
public function getTriggerChars()
{
return array("(", ")", ";", "}");
}
/**
* Implements {@link aCssParserPlugin::getTriggerStates()}.
*
* @return array
*/
public function getTriggerStates()
{
return false;
}
/**
* Implements {@link aCssParserPlugin::parse()}.
*
* @param integer $index Current index
* @param string $char Current char
* @param string $previousChar Previous char
* @return mixed TRUE will break the processing; FALSE continue with the next plugin; integer set a new index and break the processing
*/
public function parse($index, $char, $previousChar, $state)
{
// Start of expression
if ($char === "(" && strtolower(substr($this->parser->getSource(), $index - 10, 11)) === "expression(" && $state !== "T_EXPRESSION")
{
$this->parser->pushState("T_EXPRESSION");
$this->leftBraces++;
}
// Count left braces
elseif ($char === "(" && $state === "T_EXPRESSION")
{
$this->leftBraces++;
}
// Count right braces
elseif ($char === ")" && $state === "T_EXPRESSION")
{
$this->rightBraces++;
}
// Possible end of expression; if left and right braces are equal the expressen ends
elseif (($char === ";" || $char === "}") && $state === "T_EXPRESSION" && $this->leftBraces === $this->rightBraces)
{
$this->leftBraces = $this->rightBraces = 0;
$this->parser->popState();
return $index - 1;
}
else
{
return false;
}
return true;
}
}
/**
* CSS Error.
*
* @package CssMin
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssError
{
/**
* File.
*
* @var string
*/
public $File = "";
/**
* Line.
*
* @var integer
*/
public $Line = 0;
/**
* Error message.
*
* @var string
*/
public $Message = "";
/**
* Source.
*
* @var string
*/
public $Source = "";
/**
* Constructor triggering the error.
*
* @param string $message Error message
* @param string $source Corresponding line [optional]
* @return void
*/
public function __construct($file, $line, $message, $source = "")
{
$this->File = $file;
$this->Line = $line;
$this->Message = $message;
$this->Source = $source;
}
/**
* Returns the error as formatted string.
*
* @return string
*/
public function __toString()
{
return $this->Message . ($this->Source ? ": <br /><code>" . $this->Source . "</code>": "") . "<br />in file " . $this->File . " at line " . $this->Line;
}
}
/**
* This {@link aCssMinifierPlugin} will convert a color value in rgb notation to hexadecimal notation.
*
* Example:
* <code>
* color: rgb(200,60%,5);
* </code>
*
* Will get converted to:
* <code>
* color:#c89905;
* </code>
*
* @package CssMin/Minifier/Plugins
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssConvertRgbColorsMinifierPlugin extends aCssMinifierPlugin
{
/**
* Regular expression matching the value.
*
* @var string
*/
private $reMatch = "/rgb\s*\(\s*([0-9%]+)\s*,\s*([0-9%]+)\s*,\s*([0-9%]+)\s*\)/iS";
/**
* Implements {@link aCssMinifierPlugin::minify()}.
*
* @param aCssToken $token Token to process
* @return boolean Return TRUE to break the processing of this token; FALSE to continue
*/
public function apply(aCssToken &$token)
{
if (stripos($token->Value, "rgb") !== false && preg_match($this->reMatch, $token->Value, $m))
{
for ($i = 1, $l = count($m); $i < $l; $i++)
{
if (strpos("%", $m[$i]) !== false)
{
$m[$i] = substr($m[$i], 0, -1);
$m[$i] = (int) (256 * ($m[$i] / 100));
}
$m[$i] = str_pad(dechex($m[$i]), 2, "0", STR_PAD_LEFT);
}
$token->Value = str_replace($m[0], "#" . $m[1] . $m[2] . $m[3], $token->Value);
}
return false;
}
/**
* Implements {@link aMinifierPlugin::getTriggerTokens()}
*
* @return array
*/
public function getTriggerTokens()
{
return array
(
"CssAtFontFaceDeclarationToken",
"CssAtPageDeclarationToken",
"CssRulesetDeclarationToken"
);
}
}
/**
* This {@link aCssMinifierPlugin} will convert named color values to hexadecimal notation.
*
* Example:
* <code>
* color: black;
* border: 1px solid indigo;
* </code>
*
* Will get converted to:
* <code>
* color:#000;
* border:1px solid #4b0082;
* </code>
*
* @package CssMin/Minifier/Plugins
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssConvertNamedColorsMinifierPlugin extends aCssMinifierPlugin
{
/**
* Regular expression matching the value.
*
* @var string
*/
private $reMatch = null;
/**
* Transformation table used by the {@link CssConvertNamedColorsMinifierPlugin::reReplace() replacement method}.
*
* @var array
*/
private $transformation = array
(
"aliceblue" => "#f0f8ff",
"antiquewhite" => "#faebd7",
"aqua" => "#0ff",
"aquamarine" => "#7fffd4",
"azure" => "#f0ffff",
"beige" => "#f5f5dc",
"black" => "#000",
"blue" => "#00f",
"blueviolet" => "#8a2be2",
"brown" => "#a52a2a",
"burlywood" => "#deb887",
"cadetblue" => "#5f9ea0",
"chartreuse" => "#7fff00",
"chocolate" => "#d2691e",
"coral" => "#ff7f50",
"cornflowerblue" => "#6495ed",
"cornsilk" => "#fff8dc",
"crimson" => "#dc143c",
"darkblue" => "#00008b",
"darkcyan" => "#008b8b",
"darkgoldenrod" => "#b8860b",
"darkgray" => "#a9a9a9",
"darkgreen" => "#006400",
"darkkhaki" => "#bdb76b",
"darkmagenta" => "#8b008b",
"darkolivegreen" => "#556b2f",
"darkorange" => "#ff8c00",
"darkorchid" => "#9932cc",
"darkred" => "#8b0000",
"darksalmon" => "#e9967a",
"darkseagreen" => "#8fbc8f",
"darkslateblue" => "#483d8b",
"darkslategray" => "#2f4f4f",
"darkturquoise" => "#00ced1",
"darkviolet" => "#9400d3",
"deeppink" => "#ff1493",
"deepskyblue" => "#00bfff",
"dimgray" => "#696969",
"dodgerblue" => "#1e90ff",
"firebrick" => "#b22222",
"floralwhite" => "#fffaf0",
"forestgreen" => "#228b22",
"fuchsia" => "#f0f",
"gainsboro" => "#dcdcdc",
"ghostwhite" => "#f8f8ff",
"gold" => "#ffd700",
"goldenrod" => "#daa520",
"gray" => "#808080",
"green" => "#008000",
"greenyellow" => "#adff2f",
"honeydew" => "#f0fff0",
"hotpink" => "#ff69b4",
"indianred" => "#cd5c5c",
"indigo" => "#4b0082",
"ivory" => "#fffff0",
"khaki" => "#f0e68c",
"lavender" => "#e6e6fa",
"lavenderblush" => "#fff0f5",
"lawngreen" => "#7cfc00",
"lemonchiffon" => "#fffacd",
"lightblue" => "#add8e6",
"lightcoral" => "#f08080",
"lightcyan" => "#e0ffff",
"lightgoldenrodyellow" => "#fafad2",
"lightgreen" => "#90ee90",
"lightgrey" => "#d3d3d3",
"lightpink" => "#ffb6c1",
"lightsalmon" => "#ffa07a",
"lightseagreen" => "#20b2aa",
"lightskyblue" => "#87cefa",
"lightslategray" => "#789",
"lightsteelblue" => "#b0c4de",
"lightyellow" => "#ffffe0",
"lime" => "#0f0",
"limegreen" => "#32cd32",
"linen" => "#faf0e6",
"maroon" => "#800000",
"mediumaquamarine" => "#66cdaa",
"mediumblue" => "#0000cd",
"mediumorchid" => "#ba55d3",
"mediumpurple" => "#9370db",
"mediumseagreen" => "#3cb371",
"mediumslateblue" => "#7b68ee",
"mediumspringgreen" => "#00fa9a",
"mediumturquoise" => "#48d1cc",
"mediumvioletred" => "#c71585",
"midnightblue" => "#191970",
"mintcream" => "#f5fffa",
"mistyrose" => "#ffe4e1",
"moccasin" => "#ffe4b5",
"navajowhite" => "#ffdead",
"navy" => "#000080",
"oldlace" => "#fdf5e6",
"olive" => "#808000",
"olivedrab" => "#6b8e23",
"orange" => "#ffa500",
"orangered" => "#ff4500",
"orchid" => "#da70d6",
"palegoldenrod" => "#eee8aa",
"palegreen" => "#98fb98",
"paleturquoise" => "#afeeee",
"palevioletred" => "#db7093",
"papayawhip" => "#ffefd5",
"peachpuff" => "#ffdab9",
"peru" => "#cd853f",
"pink" => "#ffc0cb",
"plum" => "#dda0dd",
"powderblue" => "#b0e0e6",
"purple" => "#800080",
"red" => "#f00",
"rosybrown" => "#bc8f8f",
"royalblue" => "#4169e1",
"saddlebrown" => "#8b4513",
"salmon" => "#fa8072",
"sandybrown" => "#f4a460",
"seagreen" => "#2e8b57",
"seashell" => "#fff5ee",
"sienna" => "#a0522d",
"silver" => "#c0c0c0",
"skyblue" => "#87ceeb",
"slateblue" => "#6a5acd",
"slategray" => "#708090",
"snow" => "#fffafa",
"springgreen" => "#00ff7f",
"steelblue" => "#4682b4",
"tan" => "#d2b48c",
"teal" => "#008080",
"thistle" => "#d8bfd8",
"tomato" => "#ff6347",
"turquoise" => "#40e0d0",
"violet" => "#ee82ee",
"wheat" => "#f5deb3",
"white" => "#fff",
"whitesmoke" => "#f5f5f5",
"yellow" => "#ff0",
"yellowgreen" => "#9acd32"
);
/**
* Overwrites {@link aCssMinifierPlugin::__construct()}.
*
* The constructor will create the {@link CssConvertNamedColorsMinifierPlugin::$reMatch replace regular expression}
* based on the {@link CssConvertNamedColorsMinifierPlugin::$transformation transformation table}.
*
* @param CssMinifier $minifier The CssMinifier object of this plugin.
* @param array $configuration Plugin configuration [optional]
* @return void
*/
public function __construct(CssMinifier $minifier, array $configuration = array())
{
$this->reMatch = "/(^|\s)+(" . implode("|", array_keys($this->transformation)) . ")(\s|$)+/iS";
parent::__construct($minifier, $configuration);
}
/**
* Implements {@link aCssMinifierPlugin::minify()}.
*
* @param aCssToken $token Token to process
* @return boolean Return TRUE to break the processing of this token; FALSE to continue
*/
public function apply(aCssToken &$token)
{
$lcValue = strtolower($token->Value);
// Declaration value equals a value in the transformation table => simple replace
if (isset($this->transformation[$lcValue]))
{
$token->Value = $this->transformation[$lcValue];
}
// Declaration value contains a value in the transformation table => regular expression replace
elseif (preg_match($this->reMatch, $token->Value))
{
$token->Value = preg_replace_callback($this->reMatch, array($this, 'reReplace'), $token->Value);
}
return false;
}
/**
* Callback for replacement value.
*
* @param array $match
* @return string
*/
private function reReplace($match)
{
return $match[1] . $this->transformation[strtolower($match[2])] . $match[3];
}
/**
* Implements {@link aMinifierPlugin::getTriggerTokens()}
*
* @return array
*/
public function getTriggerTokens()
{
return array
(
"CssAtFontFaceDeclarationToken",
"CssAtPageDeclarationToken",
"CssRulesetDeclarationToken"
);
}
}
/**
* This {@link aCssMinifierFilter minifier filter} triggers on CSS Level 3 properties and will add declaration tokens
* with browser-specific properties.
*
* @package CssMin/Minifier/Filters
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssConvertLevel3PropertiesMinifierFilter extends aCssMinifierFilter
{
/**
* Css property transformations table. Used to convert CSS3 and proprietary properties to the browser-specific
* counterparts.
*
* @var array
*/
private $transformations = array
(
// Property Array(Mozilla, Webkit, Opera, Internet Explorer); NULL values are placeholders and will get ignored
"animation" => array(null, "-webkit-animation", null, null),
"animation-delay" => array(null, "-webkit-animation-delay", null, null),
"animation-direction" => array(null, "-webkit-animation-direction", null, null),
"animation-duration" => array(null, "-webkit-animation-duration", null, null),
"animation-fill-mode" => array(null, "-webkit-animation-fill-mode", null, null),
"animation-iteration-count" => array(null, "-webkit-animation-iteration-count", null, null),
"animation-name" => array(null, "-webkit-animation-name", null, null),
"animation-play-state" => array(null, "-webkit-animation-play-state", null, null),
"animation-timing-function" => array(null, "-webkit-animation-timing-function", null, null),
"appearance" => array("-moz-appearance", "-webkit-appearance", null, null),
"backface-visibility" => array(null, "-webkit-backface-visibility", null, null),
"background-clip" => array(null, "-webkit-background-clip", null, null),
"background-composite" => array(null, "-webkit-background-composite", null, null),
"background-inline-policy" => array("-moz-background-inline-policy", null, null, null),
"background-origin" => array(null, "-webkit-background-origin", null, null),
"background-position-x" => array(null, null, null, "-ms-background-position-x"),
"background-position-y" => array(null, null, null, "-ms-background-position-y"),
"background-size" => array(null, "-webkit-background-size", null, null),
"behavior" => array(null, null, null, "-ms-behavior"),
"binding" => array("-moz-binding", null, null, null),
"border-after" => array(null, "-webkit-border-after", null, null),
"border-after-color" => array(null, "-webkit-border-after-color", null, null),
"border-after-style" => array(null, "-webkit-border-after-style", null, null),
"border-after-width" => array(null, "-webkit-border-after-width", null, null),
"border-before" => array(null, "-webkit-border-before", null, null),
"border-before-color" => array(null, "-webkit-border-before-color", null, null),
"border-before-style" => array(null, "-webkit-border-before-style", null, null),
"border-before-width" => array(null, "-webkit-border-before-width", null, null),
"border-border-bottom-colors" => array("-moz-border-bottom-colors", null, null, null),
"border-bottom-left-radius" => array("-moz-border-radius-bottomleft", "-webkit-border-bottom-left-radius", null, null),
"border-bottom-right-radius" => array("-moz-border-radius-bottomright", "-webkit-border-bottom-right-radius", null, null),
"border-end" => array("-moz-border-end", "-webkit-border-end", null, null),
"border-end-color" => array("-moz-border-end-color", "-webkit-border-end-color", null, null),
"border-end-style" => array("-moz-border-end-style", "-webkit-border-end-style", null, null),
"border-end-width" => array("-moz-border-end-width", "-webkit-border-end-width", null, null),
"border-fit" => array(null, "-webkit-border-fit", null, null),
"border-horizontal-spacing" => array(null, "-webkit-border-horizontal-spacing", null, null),
"border-image" => array("-moz-border-image", "-webkit-border-image", null, null),
"border-left-colors" => array("-moz-border-left-colors", null, null, null),
"border-radius" => array("-moz-border-radius", "-webkit-border-radius", null, null),
"border-border-right-colors" => array("-moz-border-right-colors", null, null, null),
"border-start" => array("-moz-border-start", "-webkit-border-start", null, null),
"border-start-color" => array("-moz-border-start-color", "-webkit-border-start-color", null, null),
"border-start-style" => array("-moz-border-start-style", "-webkit-border-start-style", null, null),
"border-start-width" => array("-moz-border-start-width", "-webkit-border-start-width", null, null),
"border-top-colors" => array("-moz-border-top-colors", null, null, null),
"border-top-left-radius" => array("-moz-border-radius-topleft", "-webkit-border-top-left-radius", null, null),
"border-top-right-radius" => array("-moz-border-radius-topright", "-webkit-border-top-right-radius", null, null),
"border-vertical-spacing" => array(null, "-webkit-border-vertical-spacing", null, null),
"box-align" => array("-moz-box-align", "-webkit-box-align", null, null),
"box-direction" => array("-moz-box-direction", "-webkit-box-direction", null, null),
"box-flex" => array("-moz-box-flex", "-webkit-box-flex", null, null),
"box-flex-group" => array(null, "-webkit-box-flex-group", null, null),
"box-flex-lines" => array(null, "-webkit-box-flex-lines", null, null),
"box-ordinal-group" => array("-moz-box-ordinal-group", "-webkit-box-ordinal-group", null, null),
"box-orient" => array("-moz-box-orient", "-webkit-box-orient", null, null),
"box-pack" => array("-moz-box-pack", "-webkit-box-pack", null, null),
"box-reflect" => array(null, "-webkit-box-reflect", null, null),
"box-shadow" => array("-moz-box-shadow", "-webkit-box-shadow", null, null),
"box-sizing" => array("-moz-box-sizing", null, null, null),
"color-correction" => array(null, "-webkit-color-correction", null, null),
"column-break-after" => array(null, "-webkit-column-break-after", null, null),
"column-break-before" => array(null, "-webkit-column-break-before", null, null),
"column-break-inside" => array(null, "-webkit-column-break-inside", null, null),
"column-count" => array("-moz-column-count", "-webkit-column-count", null, null),
"column-gap" => array("-moz-column-gap", "-webkit-column-gap", null, null),
"column-rule" => array("-moz-column-rule", "-webkit-column-rule", null, null),
"column-rule-color" => array("-moz-column-rule-color", "-webkit-column-rule-color", null, null),
"column-rule-style" => array("-moz-column-rule-style", "-webkit-column-rule-style", null, null),
"column-rule-width" => array("-moz-column-rule-width", "-webkit-column-rule-width", null, null),
"column-span" => array(null, "-webkit-column-span", null, null),
"column-width" => array("-moz-column-width", "-webkit-column-width", null, null),
"columns" => array(null, "-webkit-columns", null, null),
"filter" => array(__CLASS__, "filter"),
"float-edge" => array("-moz-float-edge", null, null, null),
"font-feature-settings" => array("-moz-font-feature-settings", null, null, null),
"font-language-override" => array("-moz-font-language-override", null, null, null),
"font-size-delta" => array(null, "-webkit-font-size-delta", null, null),
"font-smoothing" => array(null, "-webkit-font-smoothing", null, null),
"force-broken-image-icon" => array("-moz-force-broken-image-icon", null, null, null),
"highlight" => array(null, "-webkit-highlight", null, null),
"hyphenate-character" => array(null, "-webkit-hyphenate-character", null, null),
"hyphenate-locale" => array(null, "-webkit-hyphenate-locale", null, null),
"hyphens" => array(null, "-webkit-hyphens", null, null),
"force-broken-image-icon" => array("-moz-image-region", null, null, null),
"ime-mode" => array(null, null, null, "-ms-ime-mode"),
"interpolation-mode" => array(null, null, null, "-ms-interpolation-mode"),
"layout-flow" => array(null, null, null, "-ms-layout-flow"),
"layout-grid" => array(null, null, null, "-ms-layout-grid"),
"layout-grid-char" => array(null, null, null, "-ms-layout-grid-char"),
"layout-grid-line" => array(null, null, null, "-ms-layout-grid-line"),
"layout-grid-mode" => array(null, null, null, "-ms-layout-grid-mode"),
"layout-grid-type" => array(null, null, null, "-ms-layout-grid-type"),
"line-break" => array(null, "-webkit-line-break", null, "-ms-line-break"),
"line-clamp" => array(null, "-webkit-line-clamp", null, null),
"line-grid-mode" => array(null, null, null, "-ms-line-grid-mode"),
"logical-height" => array(null, "-webkit-logical-height", null, null),
"logical-width" => array(null, "-webkit-logical-width", null, null),
"margin-after" => array(null, "-webkit-margin-after", null, null),
"margin-after-collapse" => array(null, "-webkit-margin-after-collapse", null, null),
"margin-before" => array(null, "-webkit-margin-before", null, null),
"margin-before-collapse" => array(null, "-webkit-margin-before-collapse", null, null),
"margin-bottom-collapse" => array(null, "-webkit-margin-bottom-collapse", null, null),
"margin-collapse" => array(null, "-webkit-margin-collapse", null, null),
"margin-end" => array("-moz-margin-end", "-webkit-margin-end", null, null),
"margin-start" => array("-moz-margin-start", "-webkit-margin-start", null, null),
"margin-top-collapse" => array(null, "-webkit-margin-top-collapse", null, null),
"marquee " => array(null, "-webkit-marquee", null, null),
"marquee-direction" => array(null, "-webkit-marquee-direction", null, null),
"marquee-increment" => array(null, "-webkit-marquee-increment", null, null),
"marquee-repetition" => array(null, "-webkit-marquee-repetition", null, null),
"marquee-speed" => array(null, "-webkit-marquee-speed", null, null),
"marquee-style" => array(null, "-webkit-marquee-style", null, null),
"mask" => array(null, "-webkit-mask", null, null),
"mask-attachment" => array(null, "-webkit-mask-attachment", null, null),
"mask-box-image" => array(null, "-webkit-mask-box-image", null, null),
"mask-clip" => array(null, "-webkit-mask-clip", null, null),
"mask-composite" => array(null, "-webkit-mask-composite", null, null),
"mask-image" => array(null, "-webkit-mask-image", null, null),
"mask-origin" => array(null, "-webkit-mask-origin", null, null),
"mask-position" => array(null, "-webkit-mask-position", null, null),
"mask-position-x" => array(null, "-webkit-mask-position-x", null, null),
"mask-position-y" => array(null, "-webkit-mask-position-y", null, null),
"mask-repeat" => array(null, "-webkit-mask-repeat", null, null),
"mask-repeat-x" => array(null, "-webkit-mask-repeat-x", null, null),
"mask-repeat-y" => array(null, "-webkit-mask-repeat-y", null, null),
"mask-size" => array(null, "-webkit-mask-size", null, null),
"match-nearest-mail-blockquote-color" => array(null, "-webkit-match-nearest-mail-blockquote-color", null, null),
"max-logical-height" => array(null, "-webkit-max-logical-height", null, null),
"max-logical-width" => array(null, "-webkit-max-logical-width", null, null),
"min-logical-height" => array(null, "-webkit-min-logical-height", null, null),
"min-logical-width" => array(null, "-webkit-min-logical-width", null, null),
"object-fit" => array(null, null, "-o-object-fit", null),
"object-position" => array(null, null, "-o-object-position", null),
"opacity" => array(__CLASS__, "opacity"),
"outline-radius" => array("-moz-outline-radius", null, null, null),
"outline-bottom-left-radius" => array("-moz-outline-radius-bottomleft", null, null, null),
"outline-bottom-right-radius" => array("-moz-outline-radius-bottomright", null, null, null),
"outline-top-left-radius" => array("-moz-outline-radius-topleft", null, null, null),
"outline-top-right-radius" => array("-moz-outline-radius-topright", null, null, null),
"padding-after" => array(null, "-webkit-padding-after", null, null),
"padding-before" => array(null, "-webkit-padding-before", null, null),
"padding-end" => array("-moz-padding-end", "-webkit-padding-end", null, null),
"padding-start" => array("-moz-padding-start", "-webkit-padding-start", null, null),
"perspective" => array(null, "-webkit-perspective", null, null),
"perspective-origin" => array(null, "-webkit-perspective-origin", null, null),
"perspective-origin-x" => array(null, "-webkit-perspective-origin-x", null, null),
"perspective-origin-y" => array(null, "-webkit-perspective-origin-y", null, null),
"rtl-ordering" => array(null, "-webkit-rtl-ordering", null, null),
"scrollbar-3dlight-color" => array(null, null, null, "-ms-scrollbar-3dlight-color"),
"scrollbar-arrow-color" => array(null, null, null, "-ms-scrollbar-arrow-color"),
"scrollbar-base-color" => array(null, null, null, "-ms-scrollbar-base-color"),
"scrollbar-darkshadow-color" => array(null, null, null, "-ms-scrollbar-darkshadow-color"),
"scrollbar-face-color" => array(null, null, null, "-ms-scrollbar-face-color"),
"scrollbar-highlight-color" => array(null, null, null, "-ms-scrollbar-highlight-color"),
"scrollbar-shadow-color" => array(null, null, null, "-ms-scrollbar-shadow-color"),
"scrollbar-track-color" => array(null, null, null, "-ms-scrollbar-track-color"),
"stack-sizing" => array("-moz-stack-sizing", null, null, null),
"svg-shadow" => array(null, "-webkit-svg-shadow", null, null),
"tab-size" => array("-moz-tab-size", null, "-o-tab-size", null),
"table-baseline" => array(null, null, "-o-table-baseline", null),
"text-align-last" => array(null, null, null, "-ms-text-align-last"),
"text-autospace" => array(null, null, null, "-ms-text-autospace"),
"text-combine" => array(null, "-webkit-text-combine", null, null),
"text-decorations-in-effect" => array(null, "-webkit-text-decorations-in-effect", null, null),
"text-emphasis" => array(null, "-webkit-text-emphasis", null, null),
"text-emphasis-color" => array(null, "-webkit-text-emphasis-color", null, null),
"text-emphasis-position" => array(null, "-webkit-text-emphasis-position", null, null),
"text-emphasis-style" => array(null, "-webkit-text-emphasis-style", null, null),
"text-fill-color" => array(null, "-webkit-text-fill-color", null, null),
"text-justify" => array(null, null, null, "-ms-text-justify"),
"text-kashida-space" => array(null, null, null, "-ms-text-kashida-space"),
"text-overflow" => array(null, null, "-o-text-overflow", "-ms-text-overflow"),
"text-security" => array(null, "-webkit-text-security", null, null),
"text-size-adjust" => array(null, "-webkit-text-size-adjust", null, "-ms-text-size-adjust"),
"text-stroke" => array(null, "-webkit-text-stroke", null, null),
"text-stroke-color" => array(null, "-webkit-text-stroke-color", null, null),
"text-stroke-width" => array(null, "-webkit-text-stroke-width", null, null),
"text-underline-position" => array(null, null, null, "-ms-text-underline-position"),
"transform" => array("-moz-transform", "-webkit-transform", "-o-transform", null),
"transform-origin" => array("-moz-transform-origin", "-webkit-transform-origin", "-o-transform-origin", null),
"transform-origin-x" => array(null, "-webkit-transform-origin-x", null, null),
"transform-origin-y" => array(null, "-webkit-transform-origin-y", null, null),
"transform-origin-z" => array(null, "-webkit-transform-origin-z", null, null),
"transform-style" => array(null, "-webkit-transform-style", null, null),
"transition" => array("-moz-transition", "-webkit-transition", "-o-transition", null),
"transition-delay" => array("-moz-transition-delay", "-webkit-transition-delay", "-o-transition-delay", null),
"transition-duration" => array("-moz-transition-duration", "-webkit-transition-duration", "-o-transition-duration", null),
"transition-property" => array("-moz-transition-property", "-webkit-transition-property", "-o-transition-property", null),
"transition-timing-function" => array("-moz-transition-timing-function", "-webkit-transition-timing-function", "-o-transition-timing-function", null),
"user-drag" => array(null, "-webkit-user-drag", null, null),
"user-focus" => array("-moz-user-focus", null, null, null),
"user-input" => array("-moz-user-input", null, null, null),
"user-modify" => array("-moz-user-modify", "-webkit-user-modify", null, null),
"user-select" => array("-moz-user-select", "-webkit-user-select", null, null),
"white-space" => array(__CLASS__, "whiteSpace"),
"window-shadow" => array("-moz-window-shadow", null, null, null),
"word-break" => array(null, null, null, "-ms-word-break"),
"word-wrap" => array(null, null, null, "-ms-word-wrap"),
"writing-mode" => array(null, "-webkit-writing-mode", null, "-ms-writing-mode"),
"zoom" => array(null, null, null, "-ms-zoom")
);
/**
* Implements {@link aCssMinifierFilter::filter()}.
*
* @param array $tokens Array of objects of type aCssToken
* @return integer Count of added, changed or removed tokens; a return value large than 0 will rebuild the array
*/
public function apply(array &$tokens)
{
$r = 0;
$transformations = &$this->transformations;
for ($i = 0, $l = count($tokens); $i < $l; $i++)
{
if (get_class($tokens[$i]) === "CssRulesetDeclarationToken")
{
$tProperty = $tokens[$i]->Property;
if (isset($transformations[$tProperty]))
{
$result = array();
if (is_callable($transformations[$tProperty]))
{
$result = call_user_func_array($transformations[$tProperty], array($tokens[$i]));
if (!is_array($result) && is_object($result))
{
$result = array($result);
}
}
else
{
$tValue = $tokens[$i]->Value;
$tMediaTypes = $tokens[$i]->MediaTypes;
foreach ($transformations[$tProperty] as $property)
{
if ($property !== null)
{
$result[] = new CssRulesetDeclarationToken($property, $tValue, $tMediaTypes);
}
}
}
if (count($result) > 0)
{
array_splice($tokens, $i + 1, 0, $result);
$i += count($result);
$l += count($result);
}
}
}
}
return $r;
}
/**
* Transforms the Internet Explorer specific declaration property "filter" to Internet Explorer 8+ compatible
* declaratiopn property "-ms-filter".
*
* @param aCssToken $token
* @return array
*/
private static function filter($token)
{
$r = array
(
new CssRulesetDeclarationToken("-ms-filter", "\"" . $token->Value . "\"", $token->MediaTypes),
);
return $r;
}
/**
* Transforms "opacity: {value}" into browser specific counterparts.
*
* @param aCssToken $token
* @return array
*/
private static function opacity($token)
{
// Calculate the value for Internet Explorer filter statement
$ieValue = (int) ((float) $token->Value * 100);
$r = array
(
// Internet Explorer >= 8
new CssRulesetDeclarationToken("-ms-filter", "\"alpha(opacity=" . $ieValue . ")\"", $token->MediaTypes),
// Internet Explorer >= 4 <= 7
new CssRulesetDeclarationToken("filter", "alpha(opacity=" . $ieValue . ")", $token->MediaTypes),
new CssRulesetDeclarationToken("zoom", "1", $token->MediaTypes)
);
return $r;
}
/**
* Transforms "white-space: pre-wrap" into browser specific counterparts.
*
* @param aCssToken $token
* @return array
*/
private static function whiteSpace($token)
{
if (strtolower($token->Value) === "pre-wrap")
{
$r = array
(
// Firefox < 3
new CssRulesetDeclarationToken("white-space", "-moz-pre-wrap", $token->MediaTypes),
// Webkit
new CssRulesetDeclarationToken("white-space", "-webkit-pre-wrap", $token->MediaTypes),
// Opera >= 4 <= 6
new CssRulesetDeclarationToken("white-space", "-pre-wrap", $token->MediaTypes),
// Opera >= 7
new CssRulesetDeclarationToken("white-space", "-o-pre-wrap", $token->MediaTypes),
// Internet Explorer >= 5.5
new CssRulesetDeclarationToken("word-wrap", "break-word", $token->MediaTypes)
);
return $r;
}
else
{
return array();
}
}
}
/**
* This {@link aCssMinifierFilter minifier filter} will convert @keyframes at-rule block to browser specific counterparts.
*
* @package CssMin/Minifier/Filters
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssConvertLevel3AtKeyframesMinifierFilter extends aCssMinifierFilter
{
/**
* Implements {@link aCssMinifierFilter::filter()}.
*
* @param array $tokens Array of objects of type aCssToken
* @return integer Count of added, changed or removed tokens; a return value larger than 0 will rebuild the array
*/
public function apply(array &$tokens)
{
$r = 0;
$transformations = array("-moz-keyframes", "-webkit-keyframes");
for ($i = 0, $l = count($tokens); $i < $l; $i++)
{
if (get_class($tokens[$i]) === "CssAtKeyframesStartToken")
{
for ($ii = $i; $ii < $l; $ii++)
{
if (get_class($tokens[$ii]) === "CssAtKeyframesEndToken")
{
break;
}
}
if (get_class($tokens[$ii]) === "CssAtKeyframesEndToken")
{
$add = array();
$source = array();
for ($iii = $i; $iii <= $ii; $iii++)
{
$source[] = clone($tokens[$iii]);
}
foreach ($transformations as $transformation)
{
$t = array();
foreach ($source as $token)
{
$t[] = clone($token);
}
$t[0]->AtRuleName = $transformation;
$add = array_merge($add, $t);
}
if (isset($this->configuration["RemoveSource"]) && $this->configuration["RemoveSource"] === true)
{
array_splice($tokens, $i, $ii - $i + 1, $add);
}
else
{
array_splice($tokens, $ii + 1, 0, $add);
}
$l = count($tokens);
$i = $ii + count($add);
$r += count($add);
}
}
}
return $r;
}
}
/**
* This {@link aCssMinifierPlugin} will convert a color value in hsl notation to hexadecimal notation.
*
* Example:
* <code>
* color: hsl(232,36%,48%);
* </code>
*
* Will get converted to:
* <code>
* color:#4e5aa7;
* </code>
*
* @package CssMin/Minifier/Plugins
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssConvertHslColorsMinifierPlugin extends aCssMinifierPlugin
{
/**
* Regular expression matching the value.
*
* @var string
*/
private $reMatch = "/^hsl\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*%\s*,\s*([0-9]+)\s*%\s*\)/iS";
/**
* Implements {@link aCssMinifierPlugin::minify()}.
*
* @param aCssToken $token Token to process
* @return boolean Return TRUE to break the processing of this token; FALSE to continue
*/
public function apply(aCssToken &$token)
{
if (stripos($token->Value, "hsl") !== false && preg_match($this->reMatch, $token->Value, $m))
{
$token->Value = str_replace($m[0], $this->hsl2hex($m[1], $m[2], $m[3]), $token->Value);
}
return false;
}
/**
* Implements {@link aMinifierPlugin::getTriggerTokens()}
*
* @return array
*/
public function getTriggerTokens()
{
return array
(
"CssAtFontFaceDeclarationToken",
"CssAtPageDeclarationToken",
"CssRulesetDeclarationToken"
);
}
/**
* Convert a HSL value to hexadecimal notation.
*
* Based on: {@link http://www.easyrgb.com/index.php?X=MATH&H=19#text19}.
*
* @param integer $hue Hue
* @param integer $saturation Saturation
* @param integer $lightness Lightnesss
* @return string
*/
private function hsl2hex($hue, $saturation, $lightness)
{
$hue = $hue / 360;
$saturation = $saturation / 100;
$lightness = $lightness / 100;
if ($saturation == 0)
{
$red = $lightness * 255;
$green = $lightness * 255;
$blue = $lightness * 255;
}
else
{
if ($lightness < 0.5 )
{
$v2 = $lightness * (1 + $saturation);
}
else
{
$v2 = ($lightness + $saturation) - ($saturation * $lightness);
}
$v1 = 2 * $lightness - $v2;
$red = 255 * self::hue2rgb($v1, $v2, $hue + (1 / 3));
$green = 255 * self::hue2rgb($v1, $v2, $hue);
$blue = 255 * self::hue2rgb($v1, $v2, $hue - (1 / 3));
}
return "#" . str_pad(dechex(round($red)), 2, "0", STR_PAD_LEFT) . str_pad(dechex(round($green)), 2, "0", STR_PAD_LEFT) . str_pad(dechex(round($blue)), 2, "0", STR_PAD_LEFT);
}
/**
* Apply hue to a rgb color value.
*
* @param integer $v1 Value 1
* @param integer $v2 Value 2
* @param integer $hue Hue
* @return integer
*/
private function hue2rgb($v1, $v2, $hue)
{
if ($hue < 0)
{
$hue += 1;
}
if ($hue > 1)
{
$hue -= 1;
}
if ((6 * $hue) < 1)
{
return ($v1 + ($v2 - $v1) * 6 * $hue);
}
if ((2 * $hue) < 1)
{
return ($v2);
}
if ((3 * $hue) < 2)
{
return ($v1 + ($v2 - $v1) * (( 2 / 3) - $hue) * 6);
}
return $v1;
}
}
/**
* This {@link aCssMinifierPlugin} will convert the font-weight values normal and bold to their numeric notation.
*
* Example:
* <code>
* font-weight: normal;
* font: bold 11px monospace;
* </code>
*
* Will get converted to:
* <code>
* font-weight:400;
* font:700 11px monospace;
* </code>
*
* @package CssMin/Minifier/Pluginsn
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssConvertFontWeightMinifierPlugin extends aCssMinifierPlugin
{
/**
* Array of included declaration properties this plugin will process; others declaration properties will get
* ignored.
*
* @var array
*/
private $include = array
(
"font",
"font-weight"
);
/**
* Regular expression matching the value.
*
* @var string
*/
private $reMatch = null;
/**
* Transformation table used by the {@link CssConvertFontWeightMinifierPlugin::reReplace() replacement method}.
*
* @var array
*/
private $transformation = array
(
"normal" => "400",
"bold" => "700"
);
/**
* Overwrites {@link aCssMinifierPlugin::__construct()}.
*
* The constructor will create the {@link CssConvertFontWeightMinifierPlugin::$reMatch replace regular expression}
* based on the {@link CssConvertFontWeightMinifierPlugin::$transformation transformation table}.
*
* @param CssMinifier $minifier The CssMinifier object of this plugin.
* @return void
*/
public function __construct(CssMinifier $minifier)
{
$this->reMatch = "/(^|\s)+(" . implode("|", array_keys($this->transformation)). ")(\s|$)+/iS";
parent::__construct($minifier);
}
/**
* Implements {@link aCssMinifierPlugin::minify()}.
*
* @param aCssToken $token Token to process
* @return boolean Return TRUE to break the processing of this token; FALSE to continue
*/
public function apply(aCssToken &$token)
{
if (in_array($token->Property, $this->include) && preg_match($this->reMatch, $token->Value, $m))
{
$token->Value = preg_replace_callback($this->reMatch, array($this, 'reReplace'), $token->Value);
}
return false;
}
/**
* Callback for replacement value.
*
* @param array $match
* @return string
*/
private function reReplace($match)
{
return $match[1] . $this->transformation[strtolower($match[2])] . $match[3];
}
/**
* Implements {@link aMinifierPlugin::getTriggerTokens()}
*
* @return array
*/
public function getTriggerTokens()
{
return array
(
"CssAtFontFaceDeclarationToken",
"CssAtPageDeclarationToken",
"CssRulesetDeclarationToken"
);
}
}
/**
* This {@link aCssMinifierPlugin} will compress several unit values to their short notations. Examples:
*
* <code>
* padding: 0.5em;
* border: 0px;
* margin: 0 0 0 0;
* </code>
*
* Will get compressed to:
*
* <code>
* padding:.5px;
* border:0;
* margin:0;
* </code>
*
* --
*
* @package CssMin/Minifier/Plugins
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssCompressUnitValuesMinifierPlugin extends aCssMinifierPlugin
{
/**
* Regular expression used for matching and replacing unit values.
*
* @var array
*/
private $re = array
(
"/(^| |-)0\.([0-9]+?)(0+)?(%|em|ex|px|in|cm|mm|pt|pc)/iS" => "\${1}.\${2}\${4}",
"/(^| )-?(\.?)0(%|em|ex|px|in|cm|mm|pt|pc)/iS" => "\${1}0",
);
/**
* Regular expression used for matching and replacing unit values only for non-blacklisted declarations.
*
* @var array
*/
private $reBlacklisted = array(
"/(^0\s0\s0\s0)|(^0\s0\s0$)|(^0\s0$)/iS" => "0",
);
/**
* Blacklisted properties for the above regular expression.
*
* @var array
*/
private $propertiesBlacklist = array('background-position');
/**
* Regular expression matching the value.
*
* @var string
*/
private $reMatch = "/(^| |-)0\.([0-9]+?)(0+)?(%|em|ex|px|in|cm|mm|pt|pc)|(^| )-?(\.?)0(%|em|ex|px|in|cm|mm|pt|pc)|(^0\s0\s0\s0$)|(^0\s0\s0$)|(^0\s0$)/iS";
/**
* Implements {@link aCssMinifierPlugin::minify()}.
*
* @param aCssToken $token Token to process
* @return boolean Return TRUE to break the processing of this token; FALSE to continue
*/
public function apply(aCssToken &$token)
{
if (preg_match($this->reMatch, $token->Value))
{
foreach ($this->re as $reMatch => $reReplace)
{
$token->Value = preg_replace($reMatch, $reReplace, $token->Value);
}
if (!in_array($token->Property, $this->propertiesBlacklist))
{
foreach ($this->reBlacklisted as $reMatch => $reReplace)
{
$token->Value = preg_replace($reMatch, $reReplace, $token->Value);
}
}
}
return false;
}
/**
* Implements {@link aMinifierPlugin::getTriggerTokens()}
*
* @return array
*/
public function getTriggerTokens()
{
return array
(
"CssAtFontFaceDeclarationToken",
"CssAtPageDeclarationToken",
"CssRulesetDeclarationToken"
);
}
}
/**
* This {@link aCssMinifierPlugin} compress the content of expresssion() declaration values.
*
* For compression of expressions {@link https://github.com/rgrove/jsmin-php/ JSMin} will get used. JSMin have to be
* already included or loadable via {@link http://goo.gl/JrW54 PHP autoloading}.
*
* @package CssMin/Minifier/Plugins
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssCompressExpressionValuesMinifierPlugin extends aCssMinifierPlugin
{
/**
* Implements {@link aCssMinifierPlugin::minify()}.
*
* @param aCssToken $token Token to process
* @return boolean Return TRUE to break the processing of this token; FALSE to continue
*/
public function apply(aCssToken &$token)
{
if (class_exists("JSMin") && stripos($token->Value, "expression(") !== false)
{
$value = $token->Value;
$value = substr($token->Value, stripos($token->Value, "expression(") + 10);
$value = trim(JSMin::minify($value));
$token->Value = "expression(" . $value . ")";
}
return false;
}
/**
* Implements {@link aMinifierPlugin::getTriggerTokens()}
*
* @return array
*/
public function getTriggerTokens()
{
return array
(
"CssAtFontFaceDeclarationToken",
"CssAtPageDeclarationToken",
"CssRulesetDeclarationToken"
);
}
}
/**
* This {@link aCssMinifierPlugin} will convert hexadecimal color value with 6 chars to their 3 char hexadecimal
* notation (if possible).
*
* Example:
* <code>
* color: #aabbcc;
* </code>
*
* Will get converted to:
* <code>
* color:#abc;
* </code>
*
* @package CssMin/Minifier/Plugins
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssCompressColorValuesMinifierPlugin extends aCssMinifierPlugin
{
/**
* Regular expression matching 6 char hexadecimal color values.
*
* @var string
*/
private $reMatch = "/\#([0-9a-f]{6})/iS";
/**
* Implements {@link aCssMinifierPlugin::minify()}.
*
* @param aCssToken $token Token to process
* @return boolean Return TRUE to break the processing of this token; FALSE to continue
*/
public function apply(aCssToken &$token)
{
if (strpos($token->Value, "#") !== false && preg_match($this->reMatch, $token->Value, $m))
{
$value = strtolower($m[1]);
if ($value[0] == $value[1] && $value[2] == $value[3] && $value[4] == $value[5])
{
$token->Value = str_replace($m[0], "#" . $value[0] . $value[2] . $value[4], $token->Value);
}
}
return false;
}
/**
* Implements {@link aMinifierPlugin::getTriggerTokens()}
*
* @return array
*/
public function getTriggerTokens()
{
return array
(
"CssAtFontFaceDeclarationToken",
"CssAtPageDeclarationToken",
"CssRulesetDeclarationToken"
);
}
}
/**
* This {@link aCssToken CSS token} represents a CSS comment.
*
* @package CssMin/Tokens
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssCommentToken extends aCssToken
{
/**
* Comment as Text.
*
* @var string
*/
public $Comment = "";
/**
* Set the properties of a comment token.
*
* @param string $comment Comment including comment delimiters
* @return void
*/
public function __construct($comment)
{
$this->Comment = $comment;
}
/**
* Implements {@link aCssToken::__toString()}.
*
* @return string
*/
public function __toString()
{
return $this->Comment;
}
}
/**
* {@link aCssParserPlugin Parser plugin} for parsing comments.
*
* Adds a {@link CssCommentToken} to the parser if a comment was found.
*
* @package CssMin/Parser/Plugins
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssCommentParserPlugin extends aCssParserPlugin
{
/**
* Implements {@link aCssParserPlugin::getTriggerChars()}.
*
* @return array
*/
public function getTriggerChars()
{
return array("*", "/");
}
/**
* Implements {@link aCssParserPlugin::getTriggerStates()}.
*
* @return array
*/
public function getTriggerStates()
{
return false;
}
/**
* Stored buffer for restore.
*
* @var string
*/
private $restoreBuffer = "";
/**
* Implements {@link aCssParserPlugin::parse()}.
*
* @param integer $index Current index
* @param string $char Current char
* @param string $previousChar Previous char
* @return mixed TRUE will break the processing; FALSE continue with the next plugin; integer set a new index and break the processing
*/
public function parse($index, $char, $previousChar, $state)
{
if ($char === "*" && $previousChar === "/" && $state !== "T_COMMENT")
{
$this->parser->pushState("T_COMMENT");
$this->parser->setExclusive(__CLASS__);
$this->restoreBuffer = substr($this->parser->getAndClearBuffer(), 0, -2);
}
elseif ($char === "/" && $previousChar === "*" && $state === "T_COMMENT")
{
$this->parser->popState();
$this->parser->unsetExclusive();
$this->parser->appendToken(new CssCommentToken("/*" . $this->parser->getAndClearBuffer()));
$this->parser->setBuffer($this->restoreBuffer);
}
else
{
return false;
}
return true;
}
}
/**
* This {@link aCssToken CSS token} represents the start of a @variables at-rule block.
*
* @package CssMin/Tokens
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssAtVariablesStartToken extends aCssAtBlockStartToken
{
/**
* Media types of the @variables at-rule block.
*
* @var array
*/
public $MediaTypes = array();
/**
* Set the properties of a @variables at-rule token.
*
* @param array $mediaTypes Media types
* @return void
*/
public function __construct($mediaTypes = null)
{
$this->MediaTypes = $mediaTypes ? $mediaTypes : array("all");
}
/**
* Implements {@link aCssToken::__toString()}.
*
* @return string
*/
public function __toString()
{
return "";
}
}
/**
* {@link aCssParserPlugin Parser plugin} for parsing @variables at-rule block with including declarations.
*
* Found @variables at-rule blocks will add a {@link CssAtVariablesStartToken} and {@link CssAtVariablesEndToken} to the
* parser; including declarations as {@link CssAtVariablesDeclarationToken}.
*
* @package CssMin/Parser/Plugins
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssAtVariablesParserPlugin extends aCssParserPlugin
{
/**
* Implements {@link aCssParserPlugin::getTriggerChars()}.
*
* @return array
*/
public function getTriggerChars()
{
return array("@", "{", "}", ":", ";");
}
/**
* Implements {@link aCssParserPlugin::getTriggerStates()}.
*
* @return array
*/
public function getTriggerStates()
{
return array("T_DOCUMENT", "T_AT_VARIABLES::PREPARE", "T_AT_VARIABLES", "T_AT_VARIABLES_DECLARATION");
}
/**
* Implements {@link aCssParserPlugin::parse()}.
*
* @param integer $index Current index
* @param string $char Current char
* @param string $previousChar Previous char
* @return mixed TRUE will break the processing; FALSE continue with the next plugin; integer set a new index and break the processing
*/
public function parse($index, $char, $previousChar, $state)
{
// Start of @variables at-rule block
if ($char === "@" && $state === "T_DOCUMENT" && strtolower(substr($this->parser->getSource(), $index, 10)) === "@variables")
{
$this->parser->pushState("T_AT_VARIABLES::PREPARE");
$this->parser->clearBuffer();
return $index + 10;
}
// Start of @variables declarations
elseif ($char === "{" && $state === "T_AT_VARIABLES::PREPARE")
{
$this->parser->setState("T_AT_VARIABLES");
$mediaTypes = array_filter(array_map("trim", explode(",", $this->parser->getAndClearBuffer("{"))));
$this->parser->appendToken(new CssAtVariablesStartToken($mediaTypes));
}
// Start of @variables declaration
if ($char === ":" && $state === "T_AT_VARIABLES")
{
$this->buffer = $this->parser->getAndClearBuffer(":");
$this->parser->pushState("T_AT_VARIABLES_DECLARATION");
}
// Unterminated @variables declaration
elseif ($char === ":" && $state === "T_AT_VARIABLES_DECLARATION")
{
// Ignore Internet Explorer filter declarations
if ($this->buffer === "filter")
{
return false;
}
CssMin::triggerError(new CssError(__FILE__, __LINE__, __METHOD__ . ": Unterminated @variables declaration", $this->buffer . ":" . $this->parser->getBuffer() . "_"));
}
// End of @variables declaration
elseif (($char === ";" || $char === "}") && $state === "T_AT_VARIABLES_DECLARATION")
{
$value = $this->parser->getAndClearBuffer(";}");
if (strtolower(substr($value, -10, 10)) === "!important")
{
$value = trim(substr($value, 0, -10));
$isImportant = true;
}
else
{
$isImportant = false;
}
$this->parser->popState();
$this->parser->appendToken(new CssAtVariablesDeclarationToken($this->buffer, $value, $isImportant));
$this->buffer = "";
}
// End of @variables at-rule block
elseif ($char === "}" && $state === "T_AT_VARIABLES")
{
$this->parser->popState();
$this->parser->clearBuffer();
$this->parser->appendToken(new CssAtVariablesEndToken());
}
else
{
return false;
}
return true;
}
}
/**
* This {@link aCssToken CSS token} represents the end of a @variables at-rule block.
*
* @package CssMin/Tokens
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssAtVariablesEndToken extends aCssAtBlockEndToken
{
/**
* Implements {@link aCssToken::__toString()}.
*
* @return string
*/
public function __toString()
{
return "";
}
}
/**
* This {@link aCssToken CSS token} represents a declaration of a @variables at-rule block.
*
* @package CssMin/Tokens
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssAtVariablesDeclarationToken extends aCssDeclarationToken
{
/**
* Implements {@link aCssToken::__toString()}.
*
* @return string
*/
public function __toString()
{
return "";
}
}
/**
* This {@link aCssToken CSS token} represents the start of a @page at-rule block.
*
* @package CssMin/Tokens
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssAtPageStartToken extends aCssAtBlockStartToken
{
/**
* Selector.
*
* @var string
*/
public $Selector = "";
/**
* Sets the properties of the @page at-rule.
*
* @param string $selector Selector
* @return void
*/
public function __construct($selector = "")
{
$this->Selector = $selector;
}
/**
* Implements {@link aCssToken::__toString()}.
*
* @return string
*/
public function __toString()
{
return "@page" . ($this->Selector ? " " . $this->Selector : "") . "{";
}
}
/**
* {@link aCssParserPlugin Parser plugin} for parsing @page at-rule block with including declarations.
*
* Found @page at-rule blocks will add a {@link CssAtPageStartToken} and {@link CssAtPageEndToken} to the
* parser; including declarations as {@link CssAtPageDeclarationToken}.
*
* @package CssMin/Parser/Plugins
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssAtPageParserPlugin extends aCssParserPlugin
{
/**
* Implements {@link aCssParserPlugin::getTriggerChars()}.
*
* @return array
*/
public function getTriggerChars()
{
return array("@", "{", "}", ":", ";");
}
/**
* Implements {@link aCssParserPlugin::getTriggerStates()}.
*
* @return array
*/
public function getTriggerStates()
{
return array("T_DOCUMENT", "T_AT_PAGE::SELECTOR", "T_AT_PAGE", "T_AT_PAGE_DECLARATION");
}
/**
* Implements {@link aCssParserPlugin::parse()}.
*
* @param integer $index Current index
* @param string $char Current char
* @param string $previousChar Previous char
* @return mixed TRUE will break the processing; FALSE continue with the next plugin; integer set a new index and break the processing
*/
public function parse($index, $char, $previousChar, $state)
{
// Start of @page at-rule block
if ($char === "@" && $state === "T_DOCUMENT" && strtolower(substr($this->parser->getSource(), $index, 5)) === "@page")
{
$this->parser->pushState("T_AT_PAGE::SELECTOR");
$this->parser->clearBuffer();
return $index + 5;
}
// Start of @page declarations
elseif ($char === "{" && $state === "T_AT_PAGE::SELECTOR")
{
$selector = $this->parser->getAndClearBuffer("{");
$this->parser->setState("T_AT_PAGE");
$this->parser->clearBuffer();
$this->parser->appendToken(new CssAtPageStartToken($selector));
}
// Start of @page declaration
elseif ($char === ":" && $state === "T_AT_PAGE")
{
$this->parser->pushState("T_AT_PAGE_DECLARATION");
$this->buffer = $this->parser->getAndClearBuffer(":", true);
}
// Unterminated @font-face declaration
elseif ($char === ":" && $state === "T_AT_PAGE_DECLARATION")
{
// Ignore Internet Explorer filter declarations
if ($this->buffer === "filter")
{
return false;
}
CssMin::triggerError(new CssError(__FILE__, __LINE__, __METHOD__ . ": Unterminated @page declaration", $this->buffer . ":" . $this->parser->getBuffer() . "_"));
}
// End of @page declaration
elseif (($char === ";" || $char === "}") && $state == "T_AT_PAGE_DECLARATION")
{
$value = $this->parser->getAndClearBuffer(";}");
if (strtolower(substr($value, -10, 10)) == "!important")
{
$value = trim(substr($value, 0, -10));
$isImportant = true;
}
else
{
$isImportant = false;
}
$this->parser->popState();
$this->parser->appendToken(new CssAtPageDeclarationToken($this->buffer, $value, $isImportant));
// --
if ($char === "}")
{
$this->parser->popState();
$this->parser->appendToken(new CssAtPageEndToken());
}
$this->buffer = "";
}
// End of @page at-rule block
elseif ($char === "}" && $state === "T_AT_PAGE")
{
$this->parser->popState();
$this->parser->clearBuffer();
$this->parser->appendToken(new CssAtPageEndToken());
}
else
{
return false;
}
return true;
}
}
/**
* This {@link aCssToken CSS token} represents the end of a @page at-rule block.
*
* @package CssMin/Tokens
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssAtPageEndToken extends aCssAtBlockEndToken
{
}
/**
* This {@link aCssToken CSS token} represents a declaration of a @page at-rule block.
*
* @package CssMin/Tokens
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssAtPageDeclarationToken extends aCssDeclarationToken
{
}
/**
* This {@link aCssToken CSS token} represents the start of a @media at-rule block.
*
* @package CssMin/Tokens
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssAtMediaStartToken extends aCssAtBlockStartToken
{
/**
* Sets the properties of the @media at-rule.
*
* @param array $mediaTypes Media types
* @return void
*/
public function __construct(array $mediaTypes = array())
{
$this->MediaTypes = $mediaTypes;
}
/**
* Implements {@link aCssToken::__toString()}.
*
* @return string
*/
public function __toString()
{
return "@media " . implode(",", $this->MediaTypes) . "{";
}
}
/**
* {@link aCssParserPlugin Parser plugin} for parsing @media at-rule block.
*
* Found @media at-rule blocks will add a {@link CssAtMediaStartToken} and {@link CssAtMediaEndToken} to the parser.
* This plugin will also set the the current media types using {@link CssParser::setMediaTypes()} and
* {@link CssParser::unsetMediaTypes()}.
*
* @package CssMin/Parser/Plugins
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssAtMediaParserPlugin extends aCssParserPlugin
{
/**
* Implements {@link aCssParserPlugin::getTriggerChars()}.
*
* @return array
*/
public function getTriggerChars()
{
return array("@", "{", "}");
}
/**
* Implements {@link aCssParserPlugin::getTriggerStates()}.
*
* @return array
*/
public function getTriggerStates()
{
return array("T_DOCUMENT", "T_AT_MEDIA::PREPARE", "T_AT_MEDIA");
}
/**
* Implements {@link aCssParserPlugin::parse()}.
*
* @param integer $index Current index
* @param string $char Current char
* @param string $previousChar Previous char
* @return mixed TRUE will break the processing; FALSE continue with the next plugin; integer set a new index and break the processing
*/
public function parse($index, $char, $previousChar, $state)
{
if ($char === "@" && $state === "T_DOCUMENT" && strtolower(substr($this->parser->getSource(), $index, 6)) === "@media")
{
$this->parser->pushState("T_AT_MEDIA::PREPARE");
$this->parser->clearBuffer();
return $index + 6;
}
elseif ($char === "{" && $state === "T_AT_MEDIA::PREPARE")
{
$mediaTypes = array_filter(array_map("trim", explode(",", $this->parser->getAndClearBuffer("{"))));
$this->parser->setMediaTypes($mediaTypes);
$this->parser->setState("T_AT_MEDIA");
$this->parser->appendToken(new CssAtMediaStartToken($mediaTypes));
}
elseif ($char === "}" && $state === "T_AT_MEDIA")
{
$this->parser->appendToken(new CssAtMediaEndToken());
$this->parser->clearBuffer();
$this->parser->unsetMediaTypes();
$this->parser->popState();
}
else
{
return false;
}
return true;
}
}
/**
* This {@link aCssToken CSS token} represents the end of a @media at-rule block.
*
* @package CssMin/Tokens
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssAtMediaEndToken extends aCssAtBlockEndToken
{
}
/**
* This {@link aCssToken CSS token} represents the start of a @keyframes at-rule block.
*
* @package CssMin/Tokens
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssAtKeyframesStartToken extends aCssAtBlockStartToken
{
/**
* Name of the at-rule.
*
* @var string
*/
public $AtRuleName = "keyframes";
/**
* Name
*
* @var string
*/
public $Name = "";
/**
* Sets the properties of the @page at-rule.
*
* @param string $selector Selector
* @return void
*/
public function __construct($name, $atRuleName = null)
{
$this->Name = $name;
if (!is_null($atRuleName))
{
$this->AtRuleName = $atRuleName;
}
}
/**
* Implements {@link aCssToken::__toString()}.
*
* @return string
*/
public function __toString()
{
if ($this->AtRuleName === "-moz-keyframes")
{
return "@-moz-keyframes " . $this->Name . " {";
}
return "@" . $this->AtRuleName . " " . $this->Name . "{";
}
}
/**
* This {@link aCssToken CSS token} represents the start of a ruleset of a @keyframes at-rule block.
*
* @package CssMin/Tokens
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssAtKeyframesRulesetStartToken extends aCssRulesetStartToken
{
/**
* Array of selectors.
*
* @var array
*/
public $Selectors = array();
/**
* Set the properties of a ruleset token.
*
* @param array $selectors Selectors of the ruleset
* @return void
*/
public function __construct(array $selectors = array())
{
$this->Selectors = $selectors;
}
/**
* Implements {@link aCssToken::__toString()}.
*
* @return string
*/
public function __toString()
{
return implode(",", $this->Selectors) . "{";
}
}
/**
* This {@link aCssToken CSS token} represents the end of a ruleset of a @keyframes at-rule block.
*
* @package CssMin/Tokens
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssAtKeyframesRulesetEndToken extends aCssRulesetEndToken
{
}
/**
* This {@link aCssToken CSS token} represents a ruleset declaration of a @keyframes at-rule block.
*
* @package CssMin/Tokens
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssAtKeyframesRulesetDeclarationToken extends aCssDeclarationToken
{
}
/**
* {@link aCssParserPlugin Parser plugin} for parsing @keyframes at-rule blocks, rulesets and declarations.
*
* @package CssMin/Parser/Plugins
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssAtKeyframesParserPlugin extends aCssParserPlugin
{
/**
* @var string Keyword
*/
private $atRuleName = "";
/**
* Selectors.
*
* @var array
*/
private $selectors = array();
/**
* Implements {@link aCssParserPlugin::getTriggerChars()}.
*
* @return array
*/
public function getTriggerChars()
{
return array("@", "{", "}", ":", ",", ";");
}
/**
* Implements {@link aCssParserPlugin::getTriggerStates()}.
*
* @return array
*/
public function getTriggerStates()
{
return array("T_DOCUMENT", "T_AT_KEYFRAMES::NAME", "T_AT_KEYFRAMES", "T_AT_KEYFRAMES_RULESETS", "T_AT_KEYFRAMES_RULESET", "T_AT_KEYFRAMES_RULESET_DECLARATION");
}
/**
* Implements {@link aCssParserPlugin::parse()}.
*
* @param integer $index Current index
* @param string $char Current char
* @param string $previousChar Previous char
* @return mixed TRUE will break the processing; FALSE continue with the next plugin; integer set a new index and break the processing
*/
public function parse($index, $char, $previousChar, $state)
{
// Start of @keyframes at-rule block
if ($char === "@" && $state === "T_DOCUMENT" && strtolower(substr($this->parser->getSource(), $index, 10)) === "@keyframes")
{
$this->atRuleName = "keyframes";
$this->parser->pushState("T_AT_KEYFRAMES::NAME");
$this->parser->clearBuffer();
return $index + 10;
}
// Start of @keyframes at-rule block (@-moz-keyframes)
elseif ($char === "@" && $state === "T_DOCUMENT" && strtolower(substr($this->parser->getSource(), $index, 15)) === "@-moz-keyframes")
{
$this->atRuleName = "-moz-keyframes";
$this->parser->pushState("T_AT_KEYFRAMES::NAME");
$this->parser->clearBuffer();
return $index + 15;
}
// Start of @keyframes at-rule block (@-webkit-keyframes)
elseif ($char === "@" && $state === "T_DOCUMENT" && strtolower(substr($this->parser->getSource(), $index, 18)) === "@-webkit-keyframes")
{
$this->atRuleName = "-webkit-keyframes";
$this->parser->pushState("T_AT_KEYFRAMES::NAME");
$this->parser->clearBuffer();
return $index + 18;
}
// Start of @keyframes at-rule block (@-o-keyframes)
elseif ($char === "@" && $state === "T_DOCUMENT" && strtolower(substr($this->parser->getSource(), $index, 13)) === "@-o-keyframes")
{
$this->atRuleName = "-o-keyframes";
$this->parser->pushState("T_AT_KEYFRAMES::NAME");
$this->parser->clearBuffer();
return $index + 13;
}
// Start of @keyframes at-rule block (@-ms-keyframes)
elseif ($char === "@" && $state === "T_DOCUMENT" && strtolower(substr($this->parser->getSource(), $index, 14)) === "@-ms-keyframes")
{
$this->atRuleName = "-ms-keyframes";
$this->parser->pushState("T_AT_KEYFRAMES::NAME");
$this->parser->clearBuffer();
return $index + 14;
}
// Start of @keyframes rulesets
elseif ($char === "{" && $state === "T_AT_KEYFRAMES::NAME")
{
$name = $this->parser->getAndClearBuffer("{\"'");
$this->parser->setState("T_AT_KEYFRAMES_RULESETS");
$this->parser->clearBuffer();
$this->parser->appendToken(new CssAtKeyframesStartToken($name, $this->atRuleName));
}
// Start of @keyframe ruleset and selectors
if ($char === "," && $state === "T_AT_KEYFRAMES_RULESETS")
{
$this->selectors[] = $this->parser->getAndClearBuffer(",{");
}
// Start of a @keyframes ruleset
elseif ($char === "{" && $state === "T_AT_KEYFRAMES_RULESETS")
{
if ($this->parser->getBuffer() !== "")
{
$this->selectors[] = $this->parser->getAndClearBuffer(",{");
$this->parser->pushState("T_AT_KEYFRAMES_RULESET");
$this->parser->appendToken(new CssAtKeyframesRulesetStartToken($this->selectors));
$this->selectors = array();
}
}
// Start of @keyframes ruleset declaration
elseif ($char === ":" && $state === "T_AT_KEYFRAMES_RULESET")
{
$this->parser->pushState("T_AT_KEYFRAMES_RULESET_DECLARATION");
$this->buffer = $this->parser->getAndClearBuffer(":;", true);
}
// Unterminated @keyframes ruleset declaration
elseif ($char === ":" && $state === "T_AT_KEYFRAMES_RULESET_DECLARATION")
{
// Ignore Internet Explorer filter declarations
if ($this->buffer === "filter")
{
return false;
}
CssMin::triggerError(new CssError(__FILE__, __LINE__, __METHOD__ . ": Unterminated @keyframes ruleset declaration", $this->buffer . ":" . $this->parser->getBuffer() . "_"));
}
// End of declaration
elseif (($char === ";" || $char === "}") && $state === "T_AT_KEYFRAMES_RULESET_DECLARATION")
{
$value = $this->parser->getAndClearBuffer(";}");
if (strtolower(substr($value, -10, 10)) === "!important")
{
$value = trim(substr($value, 0, -10));
$isImportant = true;
}
else
{
$isImportant = false;
}
$this->parser->popState();
$this->parser->appendToken(new CssAtKeyframesRulesetDeclarationToken($this->buffer, $value, $isImportant));
// Declaration ends with a right curly brace; so we have to end the ruleset
if ($char === "}")
{
$this->parser->appendToken(new CssAtKeyframesRulesetEndToken());
$this->parser->popState();
}
$this->buffer = "";
}
// End of @keyframes ruleset
elseif ($char === "}" && $state === "T_AT_KEYFRAMES_RULESET")
{
$this->parser->clearBuffer();
$this->parser->popState();
$this->parser->appendToken(new CssAtKeyframesRulesetEndToken());
}
// End of @keyframes rulesets
elseif ($char === "}" && $state === "T_AT_KEYFRAMES_RULESETS")
{
$this->parser->clearBuffer();
$this->parser->popState();
$this->parser->appendToken(new CssAtKeyframesEndToken());
}
else
{
return false;
}
return true;
}
}
/**
* This {@link aCssToken CSS token} represents the end of a @keyframes at-rule block.
*
* @package CssMin/Tokens
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssAtKeyframesEndToken extends aCssAtBlockEndToken
{
}
/**
* This {@link aCssToken CSS token} represents a @import at-rule.
*
* @package CssMin/Tokens
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1.b1 (2001-02-22)
*/
class CssAtImportToken extends aCssToken
{
/**
* Import path of the @import at-rule.
*
* @var string
*/
public $Import = "";
/**
* Media types of the @import at-rule.
*
* @var array
*/
public $MediaTypes = array();
/**
* Set the properties of a @import at-rule token.
*
* @param string $import Import path
* @param array $mediaTypes Media types
* @return void
*/
public function __construct($import, $mediaTypes)
{
$this->Import = $import;
$this->MediaTypes = $mediaTypes ? $mediaTypes : array();
}
/**
* Implements {@link aCssToken::__toString()}.
*
* @return string
*/
public function __toString()
{
return "@import \"" . $this->Import . "\"" . (count($this->MediaTypes) > 0 ? " " . implode(",", $this->MediaTypes) : ""). ";";
}
}
/**
* {@link aCssParserPlugin Parser plugin} for parsing @import at-rule.
*
* If a @import at-rule was found this plugin will add a {@link CssAtImportToken} to the parser.
*
* @package CssMin/Parser/Plugins
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssAtImportParserPlugin extends aCssParserPlugin
{
/**
* Implements {@link aCssParserPlugin::getTriggerChars()}.
*
* @return array
*/
public function getTriggerChars()
{
return array("@", ";", ",", "\n");
}
/**
* Implements {@link aCssParserPlugin::getTriggerStates()}.
*
* @return array
*/
public function getTriggerStates()
{
return array("T_DOCUMENT", "T_AT_IMPORT");
}
/**
* Implements {@link aCssParserPlugin::parse()}.
*
* @param integer $index Current index
* @param string $char Current char
* @param string $previousChar Previous char
* @return mixed TRUE will break the processing; FALSE continue with the next plugin; integer set a new index and break the processing
*/
public function parse($index, $char, $previousChar, $state)
{
if ($char === "@" && $state === "T_DOCUMENT" && strtolower(substr($this->parser->getSource(), $index, 7)) === "@import")
{
$this->parser->pushState("T_AT_IMPORT");
$this->parser->clearBuffer();
return $index + 7;
}
elseif (($char === ";" || $char === "\n") && $state === "T_AT_IMPORT")
{
$this->buffer = $this->parser->getAndClearBuffer(";");
$pos = false;
foreach (array(")", "\"", "'") as $needle)
{
if (($pos = strrpos($this->buffer, $needle)) !== false)
{
break;
}
}
$import = substr($this->buffer, 0, $pos + 1);
if (stripos($import, "url(") === 0)
{
$import = substr($import, 4, -1);
}
$import = trim($import, " \t\n\r\0\x0B'\"");
$mediaTypes = array_filter(array_map("trim", explode(",", trim(substr($this->buffer, $pos + 1), " \t\n\r\0\x0B{"))));
if ($pos)
{
$this->parser->appendToken(new CssAtImportToken($import, $mediaTypes));
}
else
{
CssMin::triggerError(new CssError(__FILE__, __LINE__, __METHOD__ . ": Invalid @import at-rule syntax", $this->buffer));
}
$this->parser->popState();
}
else
{
return false;
}
return true;
}
}
/**
* This {@link aCssToken CSS token} represents the start of a @font-face at-rule block.
*
* @package CssMin/Tokens
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssAtFontFaceStartToken extends aCssAtBlockStartToken
{
/**
* Implements {@link aCssToken::__toString()}.
*
* @return string
*/
public function __toString()
{
return "@font-face{";
}
}
/**
* {@link aCssParserPlugin Parser plugin} for parsing @font-face at-rule block with including declarations.
*
* Found @font-face at-rule blocks will add a {@link CssAtFontFaceStartToken} and {@link CssAtFontFaceEndToken} to the
* parser; including declarations as {@link CssAtFontFaceDeclarationToken}.
*
* @package CssMin/Parser/Plugins
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssAtFontFaceParserPlugin extends aCssParserPlugin
{
/**
* Implements {@link aCssParserPlugin::getTriggerChars()}.
*
* @return array
*/
public function getTriggerChars()
{
return array("@", "{", "}", ":", ";");
}
/**
* Implements {@link aCssParserPlugin::getTriggerStates()}.
*
* @return array
*/
public function getTriggerStates()
{
return array("T_DOCUMENT", "T_AT_FONT_FACE::PREPARE", "T_AT_FONT_FACE", "T_AT_FONT_FACE_DECLARATION");
}
/**
* Implements {@link aCssParserPlugin::parse()}.
*
* @param integer $index Current index
* @param string $char Current char
* @param string $previousChar Previous char
* @return mixed TRUE will break the processing; FALSE continue with the next plugin; integer set a new index and break the processing
*/
public function parse($index, $char, $previousChar, $state)
{
// Start of @font-face at-rule block
if ($char === "@" && $state === "T_DOCUMENT" && strtolower(substr($this->parser->getSource(), $index, 10)) === "@font-face")
{
$this->parser->pushState("T_AT_FONT_FACE::PREPARE");
$this->parser->clearBuffer();
return $index + 10 - 1;
}
// Start of @font-face declarations
elseif ($char === "{" && $state === "T_AT_FONT_FACE::PREPARE")
{
$this->parser->setState("T_AT_FONT_FACE");
$this->parser->clearBuffer();
$this->parser->appendToken(new CssAtFontFaceStartToken());
}
// Start of @font-face declaration
elseif ($char === ":" && $state === "T_AT_FONT_FACE")
{
$this->parser->pushState("T_AT_FONT_FACE_DECLARATION");
$this->buffer = $this->parser->getAndClearBuffer(":", true);
}
// Unterminated @font-face declaration
elseif ($char === ":" && $state === "T_AT_FONT_FACE_DECLARATION")
{
// Ignore Internet Explorer filter declarations
if ($this->buffer === "filter")
{
return false;
}
CssMin::triggerError(new CssError(__FILE__, __LINE__, __METHOD__ . ": Unterminated @font-face declaration", $this->buffer . ":" . $this->parser->getBuffer() . "_"));
}
// End of @font-face declaration
elseif (($char === ";" || $char === "}") && $state === "T_AT_FONT_FACE_DECLARATION")
{
$value = $this->parser->getAndClearBuffer(";}");
if (strtolower(substr($value, -10, 10)) === "!important")
{
$value = trim(substr($value, 0, -10));
$isImportant = true;
}
else
{
$isImportant = false;
}
$this->parser->popState();
$this->parser->appendToken(new CssAtFontFaceDeclarationToken($this->buffer, $value, $isImportant));
$this->buffer = "";
// --
if ($char === "}")
{
$this->parser->appendToken(new CssAtFontFaceEndToken());
$this->parser->popState();
}
}
// End of @font-face at-rule block
elseif ($char === "}" && $state === "T_AT_FONT_FACE")
{
$this->parser->appendToken(new CssAtFontFaceEndToken());
$this->parser->clearBuffer();
$this->parser->popState();
}
else
{
return false;
}
return true;
}
}
/**
* This {@link aCssToken CSS token} represents the end of a @font-face at-rule block.
*
* @package CssMin/Tokens
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssAtFontFaceEndToken extends aCssAtBlockEndToken
{
}
/**
* This {@link aCssToken CSS token} represents a declaration of a @font-face at-rule block.
*
* @package CssMin/Tokens
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssAtFontFaceDeclarationToken extends aCssDeclarationToken
{
}
/**
* This {@link aCssToken CSS token} represents a @charset at-rule.
*
* @package CssMin/Tokens
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssAtCharsetToken extends aCssToken
{
/**
* Charset of the @charset at-rule.
*
* @var string
*/
public $Charset = "";
/**
* Set the properties of @charset at-rule token.
*
* @param string $charset Charset of the @charset at-rule token
* @return void
*/
public function __construct($charset)
{
$this->Charset = $charset;
}
/**
* Implements {@link aCssToken::__toString()}.
*
* @return string
*/
public function __toString()
{
return "@charset " . $this->Charset . ";";
}
}
/**
* {@link aCssParserPlugin Parser plugin} for parsing @charset at-rule.
*
* If a @charset at-rule was found this plugin will add a {@link CssAtCharsetToken} to the parser.
*
* @package CssMin/Parser/Plugins
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssAtCharsetParserPlugin extends aCssParserPlugin
{
/**
* Implements {@link aCssParserPlugin::getTriggerChars()}.
*
* @return array
*/
public function getTriggerChars()
{
return array("@", ";", "\n");
}
/**
* Implements {@link aCssParserPlugin::getTriggerStates()}.
*
* @return array
*/
public function getTriggerStates()
{
return array("T_DOCUMENT", "T_AT_CHARSET");
}
/**
* Implements {@link aCssParserPlugin::parse()}.
*
* @param integer $index Current index
* @param string $char Current char
* @param string $previousChar Previous char
* @return mixed TRUE will break the processing; FALSE continue with the next plugin; integer set a new index and break the processing
*/
public function parse($index, $char, $previousChar, $state)
{
if ($char === "@" && $state === "T_DOCUMENT" && strtolower(substr($this->parser->getSource(), $index, 8)) === "@charset")
{
$this->parser->pushState("T_AT_CHARSET");
$this->parser->clearBuffer();
return $index + 8;
}
elseif (($char === ";" || $char === "\n") && $state === "T_AT_CHARSET")
{
$charset = $this->parser->getAndClearBuffer(";");
$this->parser->popState();
$this->parser->appendToken(new CssAtCharsetToken($charset));
}
else
{
return false;
}
return true;
}
}
?>
<?php
/*
* Copyright (C) 2016 Nicolas Grekas - p@tchwork.com
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the (at your option):
* Apache License v2.0 (see provided LICENCE.ASL20 file), or
* GNU General Public License v2.0 (see provided LICENCE.GPLv2 file).
*/
namespace Patchwork;
/*
*
* This class shrinks Javascript code
* (a process called minification nowadays)
*
* Should work with most valid Javascript code,
* even when semi-colons are missing.
*
* Features:
* - Removes comments and white spaces.
* - Renames every local vars, typically to a single character.
* - Renames also global vars, methods and properties, but only if they
* are marked special by some naming convention. By default, special
* var names begin with one or more "$", or with a single "_".
* - Renames also local/global vars found in strings,
* but only if they are marked special.
* - Keep Microsoft's conditional comments.
* - Output is optimized for later HTTP compression.
*
* Notes:
* - Source code must be parse error free before processing.
* - In order to maximise later HTTP compression (deflate, gzip),
* new variables names are chosen by considering closures,
* variables' frequency and characters' frequency.
* - If you use with/eval then be careful.
*
* Bonus:
* - Replaces false/true by !1/!0
* - Replaces new Array/Object by []/{}
* - Merges consecutive "var" declarations with commas
* - Merges consecutive concatened strings
* - Fix a bug in Safari's parser (http://forums.asp.net/thread/1585609.aspx)
* - Can replace optional semi-colons by line feeds,
* thus facilitating output debugging.
* - Keep important comments marked with /*!...
* - Treats three semi-colons ;;; like single-line comments
* (http://dean.edwards.name/packer/2/usage/#triple-semi-colon).
* - Fix special catch scope across browsers
* - Work around buggy-handling of named function expressions in IE<=8
*
* TODO?
* - foo['bar'] => foo.bar
* - {'foo':'bar'} => {foo:'bar'}
* - Dead code removal (never used function)
* - Munge primitives: var WINDOW=window, etc.
*/
class JSqueeze
{
const
SPECIAL_VAR_PACKER = '(\$+[a-zA-Z_]|_[a-zA-Z0-9$])[a-zA-Z0-9_$]*';
public
$charFreq;
protected
$strings,
$closures,
$str0,
$str1,
$argFreq,
$specialVarRx,
$keepImportantComments,
$varRx = '(?:[a-zA-Z_$])[a-zA-Z0-9_$]*',
$reserved = array(
// Literals
'true','false','null',
// ES6
'break','case','class','catch','const','continue','debugger','default','delete','do','else','export','extends','finally','for','function','if','import','in','instanceof','new','return','super','switch','this','throw','try','typeof','var','void','while','with','yield',
// Future
'enum',
// Strict mode
'implements','package','protected','static','let','interface','private','public',
// Module
'await',
// Older standards
'abstract','boolean','byte','char','double','final','float','goto','int','long','native','short','synchronized','throws','transient','volatile',
);
public function __construct()
{
$this->reserved = array_flip($this->reserved);
$this->charFreq = array_fill(0, 256, 0);
}
/**
* Squeezes a JavaScript source code.
*
* Set $singleLine to false if you want optional
* semi-colons to be replaced by line feeds.
*
* Set $keepImportantComments to false if you want /*! comments to be removed.
*
* $specialVarRx defines the regular expression of special variables names
* for global vars, methods, properties and in string substitution.
* Set it to false if you don't want any.
*
* If the analysed javascript source contains a single line comment like
* this one, then the directive will overwrite $specialVarRx:
*
* // jsqueeze.specialVarRx = your_special_var_regexp_here
*
* Only the first directive is parsed, others are ignored. It is not possible
* to redefine $specialVarRx in the middle of the javascript source.
*
* Example:
* $parser = new JSqueeze;
* $squeezed_js = $parser->squeeze($fat_js);
*/
public function squeeze($code, $singleLine = true, $keepImportantComments = true, $specialVarRx = false)
{
$code = trim($code);
if ('' === $code) {
return '';
}
$this->argFreq = array(-1 => 0);
$this->specialVarRx = $specialVarRx;
$this->keepImportantComments = !!$keepImportantComments;
if (preg_match("#//[ \t]*jsqueeze\.specialVarRx[ \t]*=[ \t]*([\"']?)(.*)\\1#i", $code, $key)) {
if (!$key[1]) {
$key[2] = trim($key[2]);
$key[1] = strtolower($key[2]);
$key[1] = $key[1] && $key[1] != 'false' && $key[1] != 'none' && $key[1] != 'off';
}
$this->specialVarRx = $key[1] ? $key[2] : false;
}
// Remove capturing parentheses
$this->specialVarRx && $this->specialVarRx = preg_replace('/(?<!\\\\)((?:\\\\\\\\)*)\((?!\?)/', '(?:', $this->specialVarRx);
false !== strpos($code, "\r") && $code = strtr(str_replace("\r\n", "\n", $code), "\r", "\n");
false !== strpos($code, "\xC2\x85") && $code = str_replace("\xC2\x85", "\n", $code); // Next Line
false !== strpos($code, "\xE2\x80\xA8") && $code = str_replace("\xE2\x80\xA8", "\n", $code); // Line Separator
false !== strpos($code, "\xE2\x80\xA9") && $code = str_replace("\xE2\x80\xA9", "\n", $code); // Paragraph Separator
list($code, $this->strings) = $this->extractStrings($code);
list($code, $this->closures) = $this->extractClosures($code);
$key = "//''\"\"#0'"; // This crap has a wonderful property: it can not happen in any valid javascript, even in strings
$this->closures[$key] = &$code;
$tree = array($key => array('parent' => false));
$this->makeVars($code, $tree[$key], $key);
$this->renameVars($tree[$key], true);
$code = substr($tree[$key]['code'], 1);
$code = preg_replace("'\breturn !'", 'return!', $code);
$code = preg_replace("'\}(?=(else|while)[^\$.a-zA-Z0-9_])'", "}\r", $code);
$code = str_replace(array_keys($this->strings), array_values($this->strings), $code);
if ($singleLine) {
$code = strtr($code, "\n", ';');
} else {
$code = str_replace("\n", ";\n", $code);
}
false !== strpos($code, "\r") && $code = strtr(trim($code), "\r", "\n");
// Cleanup memory
$this->charFreq = array_fill(0, 256, 0);
$this->strings = $this->closures = $this->argFreq = array();
$this->str0 = $this->str1 = '';
return $code;
}
protected function extractStrings($f)
{
if ($cc_on = false !== strpos($f, '@cc_on')) {
// Protect conditional comments from being removed
$f = str_replace('#', '##', $f);
$f = str_replace('/*@', '1#@', $f);
$f = preg_replace("'//@([^\n]+)'", '2#@$1@#3', $f);
$f = str_replace('@*/', '@#1', $f);
}
$len = strlen($f);
$code = str_repeat(' ', $len);
$j = 0;
$strings = array();
$K = 0;
$instr = false;
$q = array(
"'", '"',
"'" => 0,
'"' => 0,
);
// Extract strings, removes comments
for ($i = 0; $i < $len; ++$i) {
if ($instr) {
if ('//' == $instr) {
if ("\n" == $f[$i]) {
$f[$i--] = ' ';
$instr = false;
}
} elseif ($f[$i] == $instr || ('/' == $f[$i] && "/'" == $instr)) {
if ('!' == $instr) {
} elseif ('*' == $instr) {
if ('/' == $f[$i + 1]) {
++$i;
$instr = false;
}
} else {
if ("/'" == $instr) {
while (isset($f[$i + 1]) && false !== strpos('gmi', $f[$i + 1])) {
$s[] = $f[$i++];
}
$s[] = $f[$i];
}
$instr = false;
}
} elseif ('*' == $instr) {
} elseif ('!' == $instr) {
if ('*' == $f[$i] && '/' == $f[$i + 1]) {
$s[] = "*/\r";
++$i;
$instr = false;
} elseif ("\n" == $f[$i]) {
$s[] = "\r";
} else {
$s[] = $f[$i];
}
} elseif ('\\' == $f[$i]) {
++$i;
if ("\n" != $f[$i]) {
isset($q[$f[$i]]) && ++$q[$f[$i]];
$s[] = '\\'.$f[$i];
}
} elseif ('[' == $f[$i] && "/'" == $instr) {
$instr = '/[';
$s[] = '[';
} elseif (']' == $f[$i] && '/[' == $instr) {
$instr = "/'";
$s[] = ']';
} elseif ("'" == $f[$i] || '"' == $f[$i]) {
++$q[$f[$i]];
$s[] = '\\'.$f[$i];
} else {
$s[] = $f[$i];
}
} else {
switch ($f[$i]) {
case ';':
// Remove triple semi-colon
if ($i > 0 && ';' == $f[$i - 1] && $i + 1 < $len && ';' == $f[$i + 1]) {
$f[$i] = $f[$i + 1] = '/';
} else {
$code[++$j] = ';';
break;
}
case '/':
if ('*' == $f[$i + 1]) {
++$i;
$instr = '*';
if ($this->keepImportantComments && '!' == $f[$i + 1]) {
++$i;
// no break here
} else {
break;
}
} elseif ('/' == $f[$i + 1]) {
++$i;
$instr = '//';
break;
} else {
$a = $j && (' ' == $code[$j] || "\x7F" == $code[$j]) ? $code[$j - 1] : $code[$j];
if (false !== strpos('-!%&;<=>~:^+|,()*?[{} ', $a)
|| (false !== strpos('oenfd', $a)
&& preg_match(
"'(?<![\$.a-zA-Z0-9_])(do|else|return|typeof|yield[ \x7F]?\*?)[ \x7F]?$'",
substr($code, $j - 7, 8)
))
) {
if (')' === $a && $j > 1) {
$a = 1;
$k = $j - (' ' == $code[$j] || "\x7F" == $code[$j]) - 1;
while ($k >= 0 && $a) {
if ('(' === $code[$k]) {
--$a;
} elseif (')' === $code[$k]) {
++$a;
}
--$k;
}
if (!preg_match("'(?<![\$.a-zA-Z0-9_])(if|for|while)[ \x7F]?$'", substr($code, 0, $k + 1))) {
$code[++$j] = '/';
break;
}
}
$key = "//''\"\"".$K++.$instr = "/'";
$a = $j;
$code .= $key;
while (isset($key[++$j - $a - 1])) {
$code[$j] = $key[$j - $a - 1];
}
--$j;
isset($s) && ($s = implode('', $s)) && $cc_on && $this->restoreCc($s);
$strings[$key] = array('/');
$s = &$strings[$key];
} else {
$code[++$j] = '/';
}
break;
}
case "'":
case '"':
$instr = $f[$i];
$key = "//''\"\"".$K++.('!' == $instr ? ']' : "'");
$a = $j;
$code .= $key;
while (isset($key[++$j - $a - 1])) {
$code[$j] = $key[$j - $a - 1];
}
--$j;
isset($s) && ($s = implode('', $s)) && $cc_on && $this->restoreCc($s);
$strings[$key] = array();
$s = &$strings[$key];
'!' == $instr && $s[] = "\r/*!";
break;
case "\n":
if ($j > 3) {
if (' ' == $code[$j] || "\x7F" == $code[$j]) {
--$j;
}
$code[++$j] =
false !== strpos('kend+-', $code[$j - 1])
&& preg_match(
"'(?:\+\+|--|(?<![\$.a-zA-Z0-9_])(break|continue|return|yield[ \x7F]?\*?))[ \x7F]?$'",
substr($code, $j - 8, 9)
)
? ';' : "\x7F";
break;
}
case "\t": $f[$i] = ' ';
case ' ':
if (!$j || ' ' == $code[$j] || "\x7F" == $code[$j]) {
break;
}
default:
$code[++$j] = $f[$i];
}
}
}
isset($s) && ($s = implode('', $s)) && $cc_on && $this->restoreCc($s);
unset($s);
$code = substr($code, 0, $j + 1);
$cc_on && $this->restoreCc($code, false);
// Protect wanted spaces and remove unwanted ones
$code = strtr($code, "\x7F", ' ');
$code = str_replace('- -', "-\x7F-", $code);
$code = str_replace('+ +', "+\x7F+", $code);
$code = preg_replace("'(\d)\s+\.\s*([a-zA-Z\$_[(])'", "$1\x7F.$2", $code);
$code = preg_replace("# ([-!%&;<=>~:.^+|,()*?[\]{}/']+)#", '$1', $code);
$code = preg_replace("#([-!%&;<=>~:.^+|,()*?[\]{}/]+) #", '$1', $code);
$cc_on && $code = preg_replace_callback("'//[^\'].*?@#3'", function ($m) { return strtr($m[0], ' ', "\x7F"); }, $code);
// Replace new Array/Object by []/{}
false !== strpos($code, 'new Array') && $code = preg_replace("'new Array(?:\(\)|([;\])},:]))'", '[]$1', $code);
false !== strpos($code, 'new Object') && $code = preg_replace("'new Object(?:\(\)|([;\])},:]))'", '{}$1', $code);
// Add missing semi-colons after curly braces
// This adds more semi-colons than strictly needed,
// but it seems that later gzipping is favorable to the repetition of "};"
$code = preg_replace("'\}(?![:,;.()\[\]}\|&]|(else|catch|finally|while)[^\$.a-zA-Z0-9_])'", '};', $code);
// Tag possible empty instruction for easy detection
$code = preg_replace("'(?<![\$.a-zA-Z0-9_])if\('", '1#(', $code);
$code = preg_replace("'(?<![\$.a-zA-Z0-9_])for\('", '2#(', $code);
$code = preg_replace("'(?<![\$.a-zA-Z0-9_])while\('", '3#(', $code);
$forPool = array();
$instrPool = array();
$s = 0;
$f = array();
$j = -1;
// Remove as much semi-colon as possible
$len = strlen($code);
for ($i = 0; $i < $len; ++$i) {
switch ($code[$i]) {
case '(':
if ($j >= 0 && "\n" == $f[$j]) {
$f[$j] = ';';
}
++$s;
if ($i && '#' == $code[$i - 1]) {
$instrPool[$s - 1] = 1;
if ('2' == $code[$i - 2]) {
$forPool[$s] = 1;
}
}
$f[++$j] = '(';
break;
case ']':
case ')':
if ($i + 1 < $len && !isset($forPool[$s]) && !isset($instrPool[$s - 1]) && preg_match("'[a-zA-Z0-9_\$]'", $code[$i + 1])) {
$f[$j] .= $code[$i];
$f[++$j] = "\n";
} else {
$f[++$j] = $code[$i];
}
if (')' == $code[$i]) {
unset($forPool[$s]);
--$s;
}
continue 2;
case '}':
if ("\n" == $f[$j]) {
$f[$j] = '}';
} else {
$f[++$j] = '}';
}
break;
case ';':
if (isset($forPool[$s]) || isset($instrPool[$s])) {
$f[++$j] = ';';
} elseif ($j >= 0 && "\n" != $f[$j] && ';' != $f[$j]) {
$f[++$j] = "\n";
}
break;
case '#':
switch ($f[$j]) {
case '1': $f[$j] = 'if'; break 2;
case '2': $f[$j] = 'for'; break 2;
case '3': $f[$j] = 'while'; break 2;
}
case '[';
if ($j >= 0 && "\n" == $f[$j]) {
$f[$j] = ';';
}
default: $f[++$j] = $code[$i];
}
unset($instrPool[$s]);
}
$f = implode('', $f);
$cc_on && $f = str_replace('@#3', "\r", $f);
// Fix "else ;" empty instructions
$f = preg_replace("'(?<![\$.a-zA-Z0-9_])else\n'", "\n", $f);
$r1 = array( // keywords with a direct object
'case','delete','do','else','function','in','instanceof','of','break',
'new','return','throw','typeof','var','void','yield','let','if',
'const','get','set',
);
$r2 = array( // keywords with a subject
'in','instanceof','of',
);
// Fix missing semi-colons
$f = preg_replace("'(?<!(?<![a-zA-Z0-9_\$])".implode(')(?<!(?<![a-zA-Z0-9_\$])', $r1).') (?!('.implode('|', $r2).")(?![a-zA-Z0-9_\$]))'", "\n", $f);
$f = preg_replace("'(?<!(?<![a-zA-Z0-9_\$])do)(?<!(?<![a-zA-Z0-9_\$])else) if\('", "\nif(", $f);
$f = preg_replace("'(?<=--|\+\+)(?<![a-zA-Z0-9_\$])(".implode('|', $r1).")(?![a-zA-Z0-9_\$])'", "\n$1", $f);
$f = preg_replace("'(?<![a-zA-Z0-9_\$])for\neach\('", 'for each(', $f);
$f = preg_replace("'(?<![a-zA-Z0-9_\$])\n(".implode('|', $r2).")(?![a-zA-Z0-9_\$])'", '$1', $f);
// Merge strings
if ($q["'"] > $q['"']) {
$q = array($q[1], $q[0]);
}
$f = preg_replace("#//''\"\"[0-9]+'#", $q[0].'$0'.$q[0], $f);
strpos($f, $q[0].'+'.$q[0]) && $f = str_replace($q[0].'+'.$q[0], '', $f);
$len = count($strings);
foreach ($strings as $r1 => &$r2) {
$r2 = "/'" == substr($r1, -2)
? str_replace(array("\\'", '\\"'), array("'", '"'), $r2)
: str_replace('\\'.$q[1], $q[1], $r2);
}
// Restore wanted spaces
$f = strtr($f, "\x7F", ' ');
return array($f, $strings);
}
protected function extractClosures($code)
{
$code = ';'.$code;
$this->argFreq[-1] += substr_count($code, '}catch(');
if ($this->argFreq[-1]) {
// Special catch scope handling
// FIXME: this implementation doesn't work with nested catch scopes who need
// access to their parent's caught variable (but who needs that?).
$f = preg_split("@}catch\(({$this->varRx})@", $code, -1, PREG_SPLIT_DELIM_CAPTURE);
$code = 'catch$scope$var'.mt_rand();
$this->specialVarRx = $this->specialVarRx ? '(?:'.$this->specialVarRx.'|'.preg_quote($code).')' : preg_quote($code);
$i = count($f) - 1;
while ($i) {
$c = 1;
$j = 0;
$l = strlen($f[$i]);
while ($c && $j < $l) {
$s = $f[$i][$j++];
$c += '(' == $s ? 1 : (')' == $s ? -1 : 0);
}
if (!$c) {
do {
$s = $f[$i][$j++];
$c += '{' == $s ? 1 : ('}' == $s ? -1 : 0);
} while ($c && $j < $l);
}
$c = preg_quote($f[$i - 1], '#');
$f[$i - 2] .= '}catch('.preg_replace("#([.,{]?)(?<![a-zA-Z0-9_\$@]){$c}\\b#", '$1'.$code, $f[$i - 1].substr($f[$i], 0, $j)).substr($f[$i], $j);
unset($f[$i--], $f[$i--]);
}
$code = $f[0];
}
$f = preg_split("'(?<![a-zA-Z0-9_\$])((?:function[ (]|get |set ).*?\{)'", $code, -1, PREG_SPLIT_DELIM_CAPTURE);
$i = count($f) - 1;
$closures = array();
while ($i) {
$c = 1;
$j = 0;
$l = strlen($f[$i]);
while ($c && $j < $l) {
$s = $f[$i][$j++];
$c += '{' == $s ? 1 : ('}' == $s ? -1 : 0);
}
switch (substr($f[$i - 2], -1)) {
default:
if (false !== $c = strpos($f[$i - 1], ' ', 8)) {
break;
}
case false: case "\n": case ';': case '{': case '}': case ')': case ']':
$c = strpos($f[$i - 1], '(', 4);
}
$l = "//''\"\"#$i'";
$code = substr($f[$i - 1], $c);
$closures[$l] = $code.substr($f[$i], 0, $j);
$f[$i - 2] .= substr($f[$i - 1], 0, $c).$l.substr($f[$i], $j);
if ('(){' !== $code) {
$j = substr_count($code, ',');
do {
isset($this->argFreq[$j]) ? ++$this->argFreq[$j] : $this->argFreq[$j] = 1;
} while ($j--);
}
$i -= 2;
}
return array($f[0], $closures);
}
protected function makeVars($closure, &$tree, $key)
{
$tree['code'] = &$closure;
$tree['nfe'] = false;
$tree['used'] = array();
$tree['local'] = array();
// Replace multiple "var" declarations by a single one
$closure = preg_replace_callback("'(?<=[\n\{\}])var [^\n\{\};]+(?:\nvar [^\n\{\};]+)+'", array($this, 'mergeVarDeclarations'), $closure);
// Get all local vars (functions, arguments and "var" prefixed)
$vars = &$tree['local'];
if (preg_match("'^( [^(]*)?\((.*?)\)\{'", $closure, $v)) {
if ($v[1]) {
$vars[$tree['nfe'] = substr($v[1], 1)] = -1;
$tree['parent']['local'][';'.$key] = &$vars[$tree['nfe']];
}
if ($v[2]) {
$i = 0;
$v = explode(',', $v[2]);
foreach ($v as $w) {
$vars[$w] = $this->argFreq[$i++] - 1; // Give a bonus to argument variables
}
}
}
$v = preg_split("'(?<![\$.a-zA-Z0-9_])var '", $closure);
if ($i = count($v) - 1) {
$w = array();
while ($i) {
$j = $c = 0;
$l = strlen($v[$i]);
while ($j < $l) {
switch ($v[$i][$j]) {
case '(': case '[': case '{':
++$c;
break;
case ')': case ']': case '}':
if ($c-- <= 0) {
break 2;
}
break;
case ';': case "\n":
if (!$c) {
break 2;
}
default:
$c || $w[] = $v[$i][$j];
}
++$j;
}
$w[] = ',';
--$i;
}
$v = explode(',', implode('', $w));
foreach ($v as $w) {
if (preg_match("'^{$this->varRx}'", $w, $v)) {
isset($vars[$v[0]]) || $vars[$v[0]] = 0;
}
}
}
if (preg_match_all("@function ({$this->varRx})//''\"\"#@", $closure, $v)) {
foreach ($v[1] as $w) {
isset($vars[$w]) || $vars[$w] = 0;
}
}
if ($this->argFreq[-1] && preg_match_all("@}catch\(({$this->varRx})@", $closure, $v)) {
$v[0] = array();
foreach ($v[1] as $w) {
isset($v[0][$w]) ? ++$v[0][$w] : $v[0][$w] = 1;
}
foreach ($v[0] as $w => $v) {
$vars[$w] = $this->argFreq[-1] - $v;
}
}
// Get all used vars, local and non-local
$vars = &$tree['used'];
if (preg_match_all("#([.,{]?(?:[gs]et )?)(?<![a-zA-Z0-9_\$])({$this->varRx})(:?)#", $closure, $w, PREG_SET_ORDER)) {
foreach ($w as $k) {
if (isset($k[1][0]) && (',' === $k[1][0] || '{' === $k[1][0])) {
if (':' === $k[3]) {
$k = '.'.$k[2];
} elseif ('get ' === substr($k[1], 1, 4) || 'set ' === substr($k[1], 1, 4)) {
++$this->charFreq[ord($k[1][1])]; // "g" or "s"
++$this->charFreq[101]; // "e"
++$this->charFreq[116]; // "t"
$k = '.'.$k[2];
} else {
$k = $k[2];
}
} else {
$k = $k[1].$k[2];
}
isset($vars[$k]) ? ++$vars[$k] : $vars[$k] = 1;
}
}
if (preg_match_all("#//''\"\"[0-9]+(?:['!]|/')#", $closure, $w)) {
foreach ($w[0] as $a) {
$v = "'" === substr($a, -1) && "/'" !== substr($a, -2) && $this->specialVarRx
? preg_split("#([.,{]?(?:[gs]et )?(?<![a-zA-Z0-9_\$@]){$this->specialVarRx}:?)#", $this->strings[$a], -1, PREG_SPLIT_DELIM_CAPTURE)
: array($this->strings[$a]);
$a = count($v);
for ($i = 0; $i < $a; ++$i) {
$k = $v[$i];
if (1 === $i % 2) {
if (',' === $k[0] || '{' === $k[0]) {
if (':' === substr($k, -1)) {
$k = '.'.substr($k, 1, -1);
} elseif ('get ' === substr($k, 1, 4) || 'set ' === substr($k, 1, 4)) {
++$this->charFreq[ord($k[1])]; // "g" or "s"
++$this->charFreq[101]; // "e"
++$this->charFreq[116]; // "t"
$k = '.'.substr($k, 5);
} else {
$k = substr($k, 1);
}
} elseif (':' === substr($k, -1)) {
$k = substr($k, 0, -1);
}
$w = &$tree;
while (isset($w['parent']) && !(isset($w['used'][$k]) || isset($w['local'][$k]))) {
$w = &$w['parent'];
}
(isset($w['used'][$k]) || isset($w['local'][$k])) && (isset($vars[$k]) ? ++$vars[$k] : $vars[$k] = 1);
unset($w);
}
if (0 === $i % 2 || !isset($vars[$k])) {
foreach (count_chars($v[$i], 1) as $k => $w) {
$this->charFreq[$k] += $w;
}
}
}
}
}
// Propagate the usage number to parents
foreach ($vars as $w => $a) {
$k = &$tree;
$chain = array();
do {
$vars = &$k['local'];
$chain[] = &$k;
if (isset($vars[$w])) {
unset($k['used'][$w]);
if (isset($vars[$w])) {
$vars[$w] += $a;
} else {
$vars[$w] = $a;
}
$a = false;
break;
}
} while ($k['parent'] && $k = &$k['parent']);
if ($a && !$k['parent']) {
if (isset($vars[$w])) {
$vars[$w] += $a;
} else {
$vars[$w] = $a;
}
}
if (isset($tree['used'][$w]) && isset($vars[$w])) {
foreach ($chain as &$b) {
isset($b['local'][$w]) || $b['used'][$w] = &$vars[$w];
}
}
}
// Analyse childs
$tree['childs'] = array();
$vars = &$tree['childs'];
if (preg_match_all("@//''\"\"#[0-9]+'@", $closure, $w)) {
foreach ($w[0] as $a) {
$vars[$a] = array('parent' => &$tree);
$this->makeVars($this->closures[$a], $vars[$a], $a);
}
}
}
protected function mergeVarDeclarations($m)
{
return str_replace("\nvar ", ',', $m[0]);
}
protected function renameVars(&$tree, $root)
{
if ($root) {
$tree['local'] += $tree['used'];
$tree['used'] = array();
foreach ($tree['local'] as $k => $v) {
if ('.' == $k[0]) {
$k = substr($k, 1);
}
if ('true' === $k) {
$this->charFreq[48] += $v;
} elseif ('false' === $k) {
$this->charFreq[49] += $v;
} elseif (!$this->specialVarRx || !preg_match("#^{$this->specialVarRx}$#", $k)) {
foreach (count_chars($k, 1) as $k => $w) {
$this->charFreq[$k] += $w * $v;
}
} elseif (2 == strlen($k)) {
$tree['used'][] = $k[1];
}
}
$this->charFreq = $this->rsort($this->charFreq);
$this->str0 = '';
$this->str1 = '';
foreach ($this->charFreq as $k => $v) {
if (!$v) {
break;
}
$v = chr($k);
if ((64 < $k && $k < 91) || (96 < $k && $k < 123)) { // A-Z a-z
$this->str0 .= $v;
$this->str1 .= $v;
} elseif (47 < $k && $k < 58) { // 0-9
$this->str1 .= $v;
}
}
if ('' === $this->str0) {
$this->str0 = 'claspemitdbfrugnjvhowkxqyzCLASPEMITDBFRUGNJVHOWKXQYZ';
$this->str1 = $this->str0.'0123456789';
}
foreach ($tree['local'] as $var => $root) {
if ('.' != substr($var, 0, 1) && isset($tree['local'][".{$var}"])) {
$tree['local'][$var] += $tree['local'][".{$var}"];
}
}
foreach ($tree['local'] as $var => $root) {
if ('.' == substr($var, 0, 1) && isset($tree['local'][substr($var, 1)])) {
$tree['local'][$var] = $tree['local'][substr($var, 1)];
}
}
$tree['local'] = $this->rsort($tree['local']);
foreach ($tree['local'] as $var => $root) {
switch (substr($var, 0, 1)) {
case '.':
if (!isset($tree['local'][substr($var, 1)])) {
$tree['local'][$var] = '#'.($this->specialVarRx && 3 < strlen($var) && preg_match("'^\.{$this->specialVarRx}$'", $var) ? $this->getNextName($tree).'$' : substr($var, 1));
}
break;
case ';': $tree['local'][$var] = 0 === $root ? '' : $this->getNextName($tree);
case '#': break;
default:
$root = $this->specialVarRx && 2 < strlen($var) && preg_match("'^{$this->specialVarRx}$'", $var) ? $this->getNextName($tree).'$' : $var;
$tree['local'][$var] = $root;
if (isset($tree['local'][".{$var}"])) {
$tree['local'][".{$var}"] = '#'.$root;
}
}
}
foreach ($tree['local'] as $var => $root) {
$tree['local'][$var] = preg_replace("'^#'", '.', $tree['local'][$var]);
}
} else {
$tree['local'] = $this->rsort($tree['local']);
if (false !== $tree['nfe']) {
$tree['used'][] = $tree['local'][$tree['nfe']];
}
foreach ($tree['local'] as $var => $root) {
if ($tree['nfe'] !== $var) {
$tree['local'][$var] = 0 === $root ? '' : $this->getNextName($tree);
}
}
}
$this->local_tree = &$tree['local'];
$this->used_tree = &$tree['used'];
$tree['code'] = preg_replace_callback("#[.,{ ]?(?:[gs]et )?(?<![a-zA-Z0-9_\$@]){$this->varRx}:?#", array($this, 'getNewName'), $tree['code']);
if ($this->specialVarRx && preg_match_all("#//''\"\"[0-9]+'#", $tree['code'], $b)) {
foreach ($b[0] as $a) {
$this->strings[$a] = preg_replace_callback(
"#[.,{]?(?:[gs]et )?(?<![a-zA-Z0-9_\$@]){$this->specialVarRx}:?#",
array($this, 'getNewName'),
$this->strings[$a]
);
}
}
foreach ($tree['childs'] as $a => &$b) {
$this->renameVars($b, false);
$tree['code'] = str_replace($a, $b['code'], $tree['code']);
unset($tree['childs'][$a]);
}
}
protected function getNewName($m)
{
$m = $m[0];
$pre = '.' === $m[0] ? '.' : '';
$post = '';
if (',' === $m[0] || '{' === $m[0] || ' ' === $m[0]) {
$pre = $m[0];
if (':' === substr($m, -1)) {
$post = ':';
$m = (' ' !== $m[0] ? '.' : '').substr($m, 1, -1);
} elseif ('get ' === substr($m, 1, 4) || 'set ' === substr($m, 1, 4)) {
$pre .= substr($m, 1, 4);
$m = '.'.substr($m, 5);
} else {
$m = substr($m, 1);
}
} elseif (':' === substr($m, -1)) {
$post = ':';
$m = substr($m, 0, -1);
}
$post = (isset($this->reserved[$m])
? ('true' === $m ? '!0' : ('false' === $m ? '!1' : $m))
: (
isset($this->local_tree[$m])
? $this->local_tree[$m]
: (
isset($this->used_tree[$m])
? $this->used_tree[$m]
: $m
)
)
).$post;
return '' === $post ? '' : ($pre.('.' === $post[0] ? substr($post, 1) : $post));
}
protected function getNextName(&$tree = array(), &$counter = false)
{
if (false === $counter) {
$counter = &$tree['counter'];
isset($counter) || $counter = -1;
$exclude = array_flip($tree['used']);
} else {
$exclude = $tree;
}
++$counter;
$len0 = strlen($this->str0);
$len1 = strlen($this->str0);
$name = $this->str0[$counter % $len0];
$i = intval($counter / $len0) - 1;
while ($i >= 0) {
$name .= $this->str1[ $i % $len1 ];
$i = intval($i / $len1) - 1;
}
return !(isset($this->reserved[$name]) || isset($exclude[$name])) ? $name : $this->getNextName($exclude, $counter);
}
protected function restoreCc(&$s, $lf = true)
{
$lf && $s = str_replace('@#3', '', $s);
$s = str_replace('@#1', '@*/', $s);
$s = str_replace('2#@', '//@', $s);
$s = str_replace('1#@', '/*@', $s);
$s = str_replace('##', '#', $s);
}
private function rsort($array)
{
if (!$array) {
return $array;
}
$i = 0;
$tuples = array();
foreach ($array as $k => &$v) {
$tuples[] = array(++$i, $k, &$v);
}
usort($tuples, function ($a, $b) {
if ($b[2] > $a[2]) {
return 1;
}
if ($b[2] < $a[2]) {
return -1;
}
if ($b[0] > $a[0]) {
return -1;
}
if ($b[0] < $a[0]) {
return 1;
}
return 0;
});
$array = array();
foreach ($tuples as $t) {
$array[$t[1]] = &$t[2];
}
return $array;
}
}
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
/**
* File::CSV
*
* PHP versions 4 and 5
*
* Copyright (c) 1997-2008,
* Vincent Blavet <vincent@phpconcept.net>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* @category File_Formats
* @package Archive_Tar
* @author Vincent Blavet <vincent@phpconcept.net>
* @copyright 1997-2010 The Authors
* @license http://www.opensource.org/licenses/bsd-license.php New BSD License
* @version CVS: $Id$
* @link http://pear.php.net/package/Archive_Tar
*/
// If the PEAR class cannot be loaded via the autoloader,
// then try to require_once it from the PHP include path.
if (!class_exists('PEAR')) {
require_once 'PEAR.php';
}
define('ARCHIVE_TAR_ATT_SEPARATOR', 90001);
define('ARCHIVE_TAR_END_BLOCK', pack("a512", ''));
if (!function_exists('gzopen') && function_exists('gzopen64')) {
function gzopen($filename, $mode, $use_include_path = 0)
{
return gzopen64($filename, $mode, $use_include_path);
}
}
if (!function_exists('gztell') && function_exists('gztell64')) {
function gztell($zp)
{
return gztell64($zp);
}
}
if (!function_exists('gzseek') && function_exists('gzseek64')) {
function gzseek($zp, $offset, $whence = SEEK_SET)
{
return gzseek64($zp, $offset, $whence);
}
}
/**
* Creates a (compressed) Tar archive
*
* @package Archive_Tar
* @author Vincent Blavet <vincent@phpconcept.net>
* @license http://www.opensource.org/licenses/bsd-license.php New BSD License
* @version $Revision$
*/
class Archive_Tar extends PEAR
{
/**
* @var string Name of the Tar
*/
public $_tarname = '';
/**
* @var boolean if true, the Tar file will be gzipped
*/
public $_compress = false;
/**
* @var string Type of compression : 'none', 'gz', 'bz2' or 'lzma2'
*/
public $_compress_type = 'none';
/**
* @var string Explode separator
*/
public $_separator = ' ';
/**
* @var file descriptor
*/
public $_file = 0;
/**
* @var string Local Tar name of a remote Tar (http:// or ftp://)
*/
public $_temp_tarname = '';
/**
* @var string regular expression for ignoring files or directories
*/
public $_ignore_regexp = '';
/**
* @var object PEAR_Error object
*/
public $error_object = null;
/**
* Format for data extraction
*
* @var string
*/
public $_fmt ='';
/**
* Archive_Tar Class constructor. This flavour of the constructor only
* declare a new Archive_Tar object, identifying it by the name of the
* tar file.
* If the compress argument is set the tar will be read or created as a
* gzip or bz2 compressed TAR file.
*
* @param string $p_tarname The name of the tar archive to create
* @param string $p_compress can be null, 'gz', 'bz2' or 'lzma2'. This
* parameter indicates if gzip, bz2 or lzma2 compression
* is required. For compatibility reason the
* boolean value 'true' means 'gz'.
*
* @return bool
*/
public function __construct($p_tarname, $p_compress = null)
{
parent::__construct();
$this->_compress = false;
$this->_compress_type = 'none';
if (($p_compress === null) || ($p_compress == '')) {
if (@file_exists($p_tarname)) {
if ($fp = @fopen($p_tarname, "rb")) {
// look for gzip magic cookie
$data = fread($fp, 2);
fclose($fp);
if ($data == "\37\213") {
$this->_compress = true;
$this->_compress_type = 'gz';
// No sure it's enought for a magic code ....
} elseif ($data == "BZ") {
$this->_compress = true;
$this->_compress_type = 'bz2';
} elseif (file_get_contents($p_tarname, false, null, 1, 4) == '7zXZ') {
$this->_compress = true;
$this->_compress_type = 'lzma2';
}
}
} else {
// probably a remote file or some file accessible
// through a stream interface
if (substr($p_tarname, -2) == 'gz') {
$this->_compress = true;
$this->_compress_type = 'gz';
} elseif ((substr($p_tarname, -3) == 'bz2') ||
(substr($p_tarname, -2) == 'bz')
) {
$this->_compress = true;
$this->_compress_type = 'bz2';
} else {
if (substr($p_tarname, -2) == 'xz') {
$this->_compress = true;
$this->_compress_type = 'lzma2';
}
}
}
} else {
if (($p_compress === true) || ($p_compress == 'gz')) {
$this->_compress = true;
$this->_compress_type = 'gz';
} else {
if ($p_compress == 'bz2') {
$this->_compress = true;
$this->_compress_type = 'bz2';
} else {
if ($p_compress == 'lzma2') {
$this->_compress = true;
$this->_compress_type = 'lzma2';
} else {
$this->_error(
"Unsupported compression type '$p_compress'\n" .
"Supported types are 'gz', 'bz2' and 'lzma2'.\n"
);
return false;
}
}
}
}
$this->_tarname = $p_tarname;
if ($this->_compress) { // assert zlib or bz2 or xz extension support
if ($this->_compress_type == 'gz') {
$extname = 'zlib';
} else {
if ($this->_compress_type == 'bz2') {
$extname = 'bz2';
} else {
if ($this->_compress_type == 'lzma2') {
$extname = 'xz';
}
}
}
if (!extension_loaded($extname)) {
PEAR::loadExtension($extname);
}
if (!extension_loaded($extname)) {
$this->_error(
"The extension '$extname' couldn't be found.\n" .
"Please make sure your version of PHP was built " .
"with '$extname' support.\n"
);
return false;
}
}
if (version_compare(PHP_VERSION, "5.5.0-dev") < 0) {
$this->_fmt = "a100filename/a8mode/a8uid/a8gid/a12size/a12mtime/" .
"a8checksum/a1typeflag/a100link/a6magic/a2version/" .
"a32uname/a32gname/a8devmajor/a8devminor/a131prefix";
} else {
$this->_fmt = "Z100filename/Z8mode/Z8uid/Z8gid/Z12size/Z12mtime/" .
"Z8checksum/Z1typeflag/Z100link/Z6magic/Z2version/" .
"Z32uname/Z32gname/Z8devmajor/Z8devminor/Z131prefix";
}
}
public function __destruct()
{
$this->_close();
// ----- Look for a local copy to delete
if ($this->_temp_tarname != '') {
@unlink($this->_temp_tarname);
}
}
/**
* This method creates the archive file and add the files / directories
* that are listed in $p_filelist.
* If a file with the same name exist and is writable, it is replaced
* by the new tar.
* The method return false and a PEAR error text.
* The $p_filelist parameter can be an array of string, each string
* representing a filename or a directory name with their path if
* needed. It can also be a single string with names separated by a
* single blank.
* For each directory added in the archive, the files and
* sub-directories are also added.
* See also createModify() method for more details.
*
* @param array $p_filelist An array of filenames and directory names, or a
* single string with names separated by a single
* blank space.
*
* @return true on success, false on error.
* @see createModify()
*/
public function create($p_filelist)
{
return $this->createModify($p_filelist, '', '');
}
/**
* This method add the files / directories that are listed in $p_filelist in
* the archive. If the archive does not exist it is created.
* The method return false and a PEAR error text.
* The files and directories listed are only added at the end of the archive,
* even if a file with the same name is already archived.
* See also createModify() method for more details.
*
* @param array $p_filelist An array of filenames and directory names, or a
* single string with names separated by a single
* blank space.
*
* @return true on success, false on error.
* @see createModify()
* @access public
*/
public function add($p_filelist)
{
return $this->addModify($p_filelist, '', '');
}
/**
* @param string $p_path
* @param bool $p_preserve
* @return bool
*/
public function extract($p_path = '', $p_preserve = false)
{
return $this->extractModify($p_path, '', $p_preserve);
}
/**
* @return array|int
*/
public function listContent()
{
$v_list_detail = array();
if ($this->_openRead()) {
if (!$this->_extractList('', $v_list_detail, "list", '', '')) {
unset($v_list_detail);
$v_list_detail = 0;
}
$this->_close();
}
return $v_list_detail;
}
/**
* This method creates the archive file and add the files / directories
* that are listed in $p_filelist.
* If the file already exists and is writable, it is replaced by the
* new tar. It is a create and not an add. If the file exists and is
* read-only or is a directory it is not replaced. The method return
* false and a PEAR error text.
* The $p_filelist parameter can be an array of string, each string
* representing a filename or a directory name with their path if
* needed. It can also be a single string with names separated by a
* single blank.
* The path indicated in $p_remove_dir will be removed from the
* memorized path of each file / directory listed when this path
* exists. By default nothing is removed (empty path '')
* The path indicated in $p_add_dir will be added at the beginning of
* the memorized path of each file / directory listed. However it can
* be set to empty ''. The adding of a path is done after the removing
* of path.
* The path add/remove ability enables the user to prepare an archive
* for extraction in a different path than the origin files are.
* See also addModify() method for file adding properties.
*
* @param array $p_filelist An array of filenames and directory names,
* or a single string with names separated by
* a single blank space.
* @param string $p_add_dir A string which contains a path to be added
* to the memorized path of each element in
* the list.
* @param string $p_remove_dir A string which contains a path to be
* removed from the memorized path of each
* element in the list, when relevant.
*
* @return boolean true on success, false on error.
* @see addModify()
*/
public function createModify($p_filelist, $p_add_dir, $p_remove_dir = '')
{
$v_result = true;
if (!$this->_openWrite()) {
return false;
}
if ($p_filelist != '') {
if (is_array($p_filelist)) {
$v_list = $p_filelist;
} elseif (is_string($p_filelist)) {
$v_list = explode($this->_separator, $p_filelist);
} else {
$this->_cleanFile();
$this->_error('Invalid file list');
return false;
}
$v_result = $this->_addList($v_list, $p_add_dir, $p_remove_dir);
}
if ($v_result) {
$this->_writeFooter();
$this->_close();
} else {
$this->_cleanFile();
}
return $v_result;
}
/**
* This method add the files / directories listed in $p_filelist at the
* end of the existing archive. If the archive does not yet exists it
* is created.
* The $p_filelist parameter can be an array of string, each string
* representing a filename or a directory name with their path if
* needed. It can also be a single string with names separated by a
* single blank.
* The path indicated in $p_remove_dir will be removed from the
* memorized path of each file / directory listed when this path
* exists. By default nothing is removed (empty path '')
* The path indicated in $p_add_dir will be added at the beginning of
* the memorized path of each file / directory listed. However it can
* be set to empty ''. The adding of a path is done after the removing
* of path.
* The path add/remove ability enables the user to prepare an archive
* for extraction in a different path than the origin files are.
* If a file/dir is already in the archive it will only be added at the
* end of the archive. There is no update of the existing archived
* file/dir. However while extracting the archive, the last file will
* replace the first one. This results in a none optimization of the
* archive size.
* If a file/dir does not exist the file/dir is ignored. However an
* error text is send to PEAR error.
* If a file/dir is not readable the file/dir is ignored. However an
* error text is send to PEAR error.
*
* @param array $p_filelist An array of filenames and directory
* names, or a single string with names
* separated by a single blank space.
* @param string $p_add_dir A string which contains a path to be
* added to the memorized path of each
* element in the list.
* @param string $p_remove_dir A string which contains a path to be
* removed from the memorized path of
* each element in the list, when
* relevant.
*
* @return true on success, false on error.
*/
public function addModify($p_filelist, $p_add_dir, $p_remove_dir = '')
{
$v_result = true;
if (!$this->_isArchive()) {
$v_result = $this->createModify(
$p_filelist,
$p_add_dir,
$p_remove_dir
);
} else {
if (is_array($p_filelist)) {
$v_list = $p_filelist;
} elseif (is_string($p_filelist)) {
$v_list = explode($this->_separator, $p_filelist);
} else {
$this->_error('Invalid file list');
return false;
}
$v_result = $this->_append($v_list, $p_add_dir, $p_remove_dir);
}
return $v_result;
}
/**
* This method add a single string as a file at the
* end of the existing archive. If the archive does not yet exists it
* is created.
*
* @param string $p_filename A string which contains the full
* filename path that will be associated
* with the string.
* @param string $p_string The content of the file added in
* the archive.
* @param bool|int $p_datetime A custom date/time (unix timestamp)
* for the file (optional).
* @param array $p_params An array of optional params:
* stamp => the datetime (replaces
* datetime above if it exists)
* mode => the permissions on the
* file (600 by default)
* type => is this a link? See the
* tar specification for details.
* (default = regular file)
* uid => the user ID of the file
* (default = 0 = root)
* gid => the group ID of the file
* (default = 0 = root)
*
* @return true on success, false on error.
*/
public function addString($p_filename, $p_string, $p_datetime = false, $p_params = array())
{
$p_stamp = @$p_params["stamp"] ? $p_params["stamp"] : ($p_datetime ? $p_datetime : time());
$p_mode = @$p_params["mode"] ? $p_params["mode"] : 0600;
$p_type = @$p_params["type"] ? $p_params["type"] : "";
$p_uid = @$p_params["uid"] ? $p_params["uid"] : "";
$p_gid = @$p_params["gid"] ? $p_params["gid"] : "";
$v_result = true;
if (!$this->_isArchive()) {
if (!$this->_openWrite()) {
return false;
}
$this->_close();
}
if (!$this->_openAppend()) {
return false;
}
// Need to check the get back to the temporary file ? ....
$v_result = $this->_addString($p_filename, $p_string, $p_datetime, $p_params);
$this->_writeFooter();
$this->_close();
return $v_result;
}
/**
* This method extract all the content of the archive in the directory
* indicated by $p_path. When relevant the memorized path of the
* files/dir can be modified by removing the $p_remove_path path at the
* beginning of the file/dir path.
* While extracting a file, if the directory path does not exists it is
* created.
* While extracting a file, if the file already exists it is replaced
* without looking for last modification date.
* While extracting a file, if the file already exists and is write
* protected, the extraction is aborted.
* While extracting a file, if a directory with the same name already
* exists, the extraction is aborted.
* While extracting a directory, if a file with the same name already
* exists, the extraction is aborted.
* While extracting a file/directory if the destination directory exist
* and is write protected, or does not exist but can not be created,
* the extraction is aborted.
* If after extraction an extracted file does not show the correct
* stored file size, the extraction is aborted.
* When the extraction is aborted, a PEAR error text is set and false
* is returned. However the result can be a partial extraction that may
* need to be manually cleaned.
*
* @param string $p_path The path of the directory where the
* files/dir need to by extracted.
* @param string $p_remove_path Part of the memorized path that can be
* removed if present at the beginning of
* the file/dir path.
* @param boolean $p_preserve Preserve user/group ownership of files
*
* @return boolean true on success, false on error.
* @see extractList()
*/
public function extractModify($p_path, $p_remove_path, $p_preserve = false)
{
$v_result = true;
$v_list_detail = array();
if ($v_result = $this->_openRead()) {
$v_result = $this->_extractList(
$p_path,
$v_list_detail,
"complete",
0,
$p_remove_path,
$p_preserve
);
$this->_close();
}
return $v_result;
}
/**
* This method extract from the archive one file identified by $p_filename.
* The return value is a string with the file content, or NULL on error.
*
* @param string $p_filename The path of the file to extract in a string.
*
* @return a string with the file content or NULL.
*/
public function extractInString($p_filename)
{
if ($this->_openRead()) {
$v_result = $this->_extractInString($p_filename);
$this->_close();
} else {
$v_result = null;
}
return $v_result;
}
/**
* This method extract from the archive only the files indicated in the
* $p_filelist. These files are extracted in the current directory or
* in the directory indicated by the optional $p_path parameter.
* If indicated the $p_remove_path can be used in the same way as it is
* used in extractModify() method.
*
* @param array $p_filelist An array of filenames and directory names,
* or a single string with names separated
* by a single blank space.
* @param string $p_path The path of the directory where the
* files/dir need to by extracted.
* @param string $p_remove_path Part of the memorized path that can be
* removed if present at the beginning of
* the file/dir path.
* @param boolean $p_preserve Preserve user/group ownership of files
*
* @return true on success, false on error.
* @see extractModify()
*/
public function extractList($p_filelist, $p_path = '', $p_remove_path = '', $p_preserve = false)
{
$v_result = true;
$v_list_detail = array();
if (is_array($p_filelist)) {
$v_list = $p_filelist;
} elseif (is_string($p_filelist)) {
$v_list = explode($this->_separator, $p_filelist);
} else {
$this->_error('Invalid string list');
return false;
}
if ($v_result = $this->_openRead()) {
$v_result = $this->_extractList(
$p_path,
$v_list_detail,
"partial",
$v_list,
$p_remove_path,
$p_preserve
);
$this->_close();
}
return $v_result;
}
/**
* This method set specific attributes of the archive. It uses a variable
* list of parameters, in the format attribute code + attribute values :
* $arch->setAttribute(ARCHIVE_TAR_ATT_SEPARATOR, ',');
*
* @return true on success, false on error.
*/
public function setAttribute()
{
$v_result = true;
// ----- Get the number of variable list of arguments
if (($v_size = func_num_args()) == 0) {
return true;
}
// ----- Get the arguments
$v_att_list = & func_get_args();
// ----- Read the attributes
$i = 0;
while ($i < $v_size) {
// ----- Look for next option
switch ($v_att_list[$i]) {
// ----- Look for options that request a string value
case ARCHIVE_TAR_ATT_SEPARATOR :
// ----- Check the number of parameters
if (($i + 1) >= $v_size) {
$this->_error(
'Invalid number of parameters for '
. 'attribute ARCHIVE_TAR_ATT_SEPARATOR'
);
return false;
}
// ----- Get the value
$this->_separator = $v_att_list[$i + 1];
$i++;
break;
default :
$this->_error('Unknown attribute code ' . $v_att_list[$i] . '');
return false;
}
// ----- Next attribute
$i++;
}
return $v_result;
}
/**
* This method sets the regular expression for ignoring files and directories
* at import, for example:
* $arch->setIgnoreRegexp("#CVS|\.svn#");
*
* @param string $regexp regular expression defining which files or directories to ignore
*/
public function setIgnoreRegexp($regexp)
{
$this->_ignore_regexp = $regexp;
}
/**
* This method sets the regular expression for ignoring all files and directories
* matching the filenames in the array list at import, for example:
* $arch->setIgnoreList(array('CVS', '.svn', 'bin/tool'));
*
* @param array $list a list of file or directory names to ignore
*
* @access public
*/
public function setIgnoreList($list)
{
$regexp = str_replace(array('#', '.', '^', '$'), array('\#', '\.', '\^', '\$'), $list);
$regexp = '#/' . join('$|/', $list) . '#';
$this->setIgnoreRegexp($regexp);
}
/**
* @param string $p_message
*/
public function _error($p_message)
{
$this->error_object = $this->raiseError($p_message);
}
/**
* @param string $p_message
*/
public function _warning($p_message)
{
$this->error_object = $this->raiseError($p_message);
}
/**
* @param string $p_filename
* @return bool
*/
public function _isArchive($p_filename = null)
{
if ($p_filename == null) {
$p_filename = $this->_tarname;
}
clearstatcache();
return @is_file($p_filename) && !@is_link($p_filename);
}
/**
* @return bool
*/
public function _openWrite()
{
if ($this->_compress_type == 'gz' && function_exists('gzopen')) {
$this->_file = @gzopen($this->_tarname, "wb9");
} else {
if ($this->_compress_type == 'bz2' && function_exists('bzopen')) {
$this->_file = @bzopen($this->_tarname, "w");
} else {
if ($this->_compress_type == 'lzma2' && function_exists('xzopen')) {
$this->_file = @xzopen($this->_tarname, 'w');
} else {
if ($this->_compress_type == 'none') {
$this->_file = @fopen($this->_tarname, "wb");
} else {
$this->_error(
'Unknown or missing compression type ('
. $this->_compress_type . ')'
);
return false;
}
}
}
}
if ($this->_file == 0) {
$this->_error(
'Unable to open in write mode \''
. $this->_tarname . '\''
);
return false;
}
return true;
}
/**
* @return bool
*/
public function _openRead()
{
if (strtolower(substr($this->_tarname, 0, 7)) == 'http://') {
// ----- Look if a local copy need to be done
if ($this->_temp_tarname == '') {
$this->_temp_tarname = uniqid('tar') . '.tmp';
if (!$v_file_from = @fopen($this->_tarname, 'rb')) {
$this->_error(
'Unable to open in read mode \''
. $this->_tarname . '\''
);
$this->_temp_tarname = '';
return false;
}
if (!$v_file_to = @fopen($this->_temp_tarname, 'wb')) {
$this->_error(
'Unable to open in write mode \''
. $this->_temp_tarname . '\''
);
$this->_temp_tarname = '';
return false;
}
while ($v_data = @fread($v_file_from, 1024)) {
@fwrite($v_file_to, $v_data);
}
@fclose($v_file_from);
@fclose($v_file_to);
}
// ----- File to open if the local copy
$v_filename = $this->_temp_tarname;
} else {
// ----- File to open if the normal Tar file
$v_filename = $this->_tarname;
}
if ($this->_compress_type == 'gz' && function_exists('gzopen')) {
$this->_file = @gzopen($v_filename, "rb");
} else {
if ($this->_compress_type == 'bz2' && function_exists('bzopen')) {
$this->_file = @bzopen($v_filename, "r");
} else {
if ($this->_compress_type == 'lzma2' && function_exists('xzopen')) {
$this->_file = @xzopen($v_filename, "r");
} else {
if ($this->_compress_type == 'none') {
$this->_file = @fopen($v_filename, "rb");
} else {
$this->_error(
'Unknown or missing compression type ('
. $this->_compress_type . ')'
);
return false;
}
}
}
}
if ($this->_file == 0) {
$this->_error('Unable to open in read mode \'' . $v_filename . '\'');
return false;
}
return true;
}
/**
* @return bool
*/
public function _openReadWrite()
{
if ($this->_compress_type == 'gz') {
$this->_file = @gzopen($this->_tarname, "r+b");
} else {
if ($this->_compress_type == 'bz2') {
$this->_error(
'Unable to open bz2 in read/write mode \''
. $this->_tarname . '\' (limitation of bz2 extension)'
);
return false;
} else {
if ($this->_compress_type == 'lzma2') {
$this->_error(
'Unable to open lzma2 in read/write mode \''
. $this->_tarname . '\' (limitation of lzma2 extension)'
);
return false;
} else {
if ($this->_compress_type == 'none') {
$this->_file = @fopen($this->_tarname, "r+b");
} else {
$this->_error(
'Unknown or missing compression type ('
. $this->_compress_type . ')'
);
return false;
}
}
}
}
if ($this->_file == 0) {
$this->_error(
'Unable to open in read/write mode \''
. $this->_tarname . '\''
);
return false;
}
return true;
}
/**
* @return bool
*/
public function _close()
{
//if (isset($this->_file)) {
if (is_resource($this->_file)) {
if ($this->_compress_type == 'gz') {
@gzclose($this->_file);
} else {
if ($this->_compress_type == 'bz2') {
@bzclose($this->_file);
} else {
if ($this->_compress_type == 'lzma2') {
@xzclose($this->_file);
} else {
if ($this->_compress_type == 'none') {
@fclose($this->_file);
} else {
$this->_error(
'Unknown or missing compression type ('
. $this->_compress_type . ')'
);
}
}
}
}
$this->_file = 0;
}
// ----- Look if a local copy need to be erase
// Note that it might be interesting to keep the url for a time : ToDo
if ($this->_temp_tarname != '') {
@unlink($this->_temp_tarname);
$this->_temp_tarname = '';
}
return true;
}
/**
* @return bool
*/
public function _cleanFile()
{
$this->_close();
// ----- Look for a local copy
if ($this->_temp_tarname != '') {
// ----- Remove the local copy but not the remote tarname
@unlink($this->_temp_tarname);
$this->_temp_tarname = '';
} else {
// ----- Remove the local tarname file
@unlink($this->_tarname);
}
$this->_tarname = '';
return true;
}
/**
* @param mixed $p_binary_data
* @param integer $p_len
* @return bool
*/
public function _writeBlock($p_binary_data, $p_len = null)
{
if (is_resource($this->_file)) {
if ($p_len === null) {
if ($this->_compress_type == 'gz') {
@gzputs($this->_file, $p_binary_data);
} else {
if ($this->_compress_type == 'bz2') {
@bzwrite($this->_file, $p_binary_data);
} else {
if ($this->_compress_type == 'lzma2') {
@xzwrite($this->_file, $p_binary_data);
} else {
if ($this->_compress_type == 'none') {
@fputs($this->_file, $p_binary_data);
} else {
$this->_error(
'Unknown or missing compression type ('
. $this->_compress_type . ')'
);
}
}
}
}
} else {
if ($this->_compress_type == 'gz') {
@gzputs($this->_file, $p_binary_data, $p_len);
} else {
if ($this->_compress_type == 'bz2') {
@bzwrite($this->_file, $p_binary_data, $p_len);
} else {
if ($this->_compress_type == 'lzma2') {
@xzwrite($this->_file, $p_binary_data, $p_len);
} else {
if ($this->_compress_type == 'none') {
@fputs($this->_file, $p_binary_data, $p_len);
} else {
$this->_error(
'Unknown or missing compression type ('
. $this->_compress_type . ')'
);
}
}
}
}
}
}
return true;
}
/**
* @return null|string
*/
public function _readBlock()
{
$v_block = null;
if (is_resource($this->_file)) {
if ($this->_compress_type == 'gz') {
$v_block = @gzread($this->_file, 512);
} else {
if ($this->_compress_type == 'bz2') {
$v_block = @bzread($this->_file, 512);
} else {
if ($this->_compress_type == 'lzma2') {
$v_block = @xzread($this->_file, 512);
} else {
if ($this->_compress_type == 'none') {
$v_block = @fread($this->_file, 512);
} else {
$this->_error(
'Unknown or missing compression type ('
. $this->_compress_type . ')'
);
}
}
}
}
}
return $v_block;
}
/**
* @param null $p_len
* @return bool
*/
public function _jumpBlock($p_len = null)
{
if (is_resource($this->_file)) {
if ($p_len === null) {
$p_len = 1;
}
if ($this->_compress_type == 'gz') {
@gzseek($this->_file, gztell($this->_file) + ($p_len * 512));
} else {
if ($this->_compress_type == 'bz2') {
// ----- Replace missing bztell() and bzseek()
for ($i = 0; $i < $p_len; $i++) {
$this->_readBlock();
}
} else {
if ($this->_compress_type == 'lzma2') {
// ----- Replace missing xztell() and xzseek()
for ($i = 0; $i < $p_len; $i++) {
$this->_readBlock();
}
} else {
if ($this->_compress_type == 'none') {
@fseek($this->_file, $p_len * 512, SEEK_CUR);
} else {
$this->_error(
'Unknown or missing compression type ('
. $this->_compress_type . ')'
);
}
}
}
}
}
return true;
}
/**
* @return bool
*/
public function _writeFooter()
{
if (is_resource($this->_file)) {
// ----- Write the last 0 filled block for end of archive
$v_binary_data = pack('a1024', '');
$this->_writeBlock($v_binary_data);
}
return true;
}
/**
* @param array $p_list
* @param string $p_add_dir
* @param string $p_remove_dir
* @return bool
*/
public function _addList($p_list, $p_add_dir, $p_remove_dir)
{
$v_result = true;
$v_header = array();
// ----- Remove potential windows directory separator
$p_add_dir = $this->_translateWinPath($p_add_dir);
$p_remove_dir = $this->_translateWinPath($p_remove_dir, false);
if (!$this->_file) {
$this->_error('Invalid file descriptor');
return false;
}
if (sizeof($p_list) == 0) {
return true;
}
foreach ($p_list as $v_filename) {
if (!$v_result) {
break;
}
// ----- Skip the current tar name
if ($v_filename == $this->_tarname) {
continue;
}
if ($v_filename == '') {
continue;
}
// ----- ignore files and directories matching the ignore regular expression
if ($this->_ignore_regexp && preg_match($this->_ignore_regexp, '/' . $v_filename)) {
$this->_warning("File '$v_filename' ignored");
continue;
}
if (!file_exists($v_filename) && !is_link($v_filename)) {
$this->_warning("File '$v_filename' does not exist");
continue;
}
// ----- Add the file or directory header
if (!$this->_addFile($v_filename, $v_header, $p_add_dir, $p_remove_dir)) {
return false;
}
if (@is_dir($v_filename) && !@is_link($v_filename)) {
if (!($p_hdir = opendir($v_filename))) {
$this->_warning("Directory '$v_filename' can not be read");
continue;
}
while (false !== ($p_hitem = readdir($p_hdir))) {
if (($p_hitem != '.') && ($p_hitem != '..')) {
if ($v_filename != ".") {
$p_temp_list[0] = $v_filename . '/' . $p_hitem;
} else {
$p_temp_list[0] = $p_hitem;
}
$v_result = $this->_addList(
$p_temp_list,
$p_add_dir,
$p_remove_dir
);
}
}
unset($p_temp_list);
unset($p_hdir);
unset($p_hitem);
}
}
return $v_result;
}
/**
* @param string $p_filename
* @param mixed $p_header
* @param string $p_add_dir
* @param string $p_remove_dir
* @param null $v_stored_filename
* @return bool
*/
public function _addFile($p_filename, &$p_header, $p_add_dir, $p_remove_dir, $v_stored_filename = null)
{
if (!$this->_file) {
$this->_error('Invalid file descriptor');
return false;
}
if ($p_filename == '') {
$this->_error('Invalid file name');
return false;
}
if (is_null($v_stored_filename)) {
// ----- Calculate the stored filename
$p_filename = $this->_translateWinPath($p_filename, false);
$v_stored_filename = $p_filename;
if (strcmp($p_filename, $p_remove_dir) == 0) {
return true;
}
if ($p_remove_dir != '') {
if (substr($p_remove_dir, -1) != '/') {
$p_remove_dir .= '/';
}
if (substr($p_filename, 0, strlen($p_remove_dir)) == $p_remove_dir) {
$v_stored_filename = substr($p_filename, strlen($p_remove_dir));
}
}
$v_stored_filename = $this->_translateWinPath($v_stored_filename);
if ($p_add_dir != '') {
if (substr($p_add_dir, -1) == '/') {
$v_stored_filename = $p_add_dir . $v_stored_filename;
} else {
$v_stored_filename = $p_add_dir . '/' . $v_stored_filename;
}
}
$v_stored_filename = $this->_pathReduction($v_stored_filename);
}
if ($this->_isArchive($p_filename)) {
if (($v_file = @fopen($p_filename, "rb")) == 0) {
$this->_warning(
"Unable to open file '" . $p_filename
. "' in binary read mode"
);
return true;
}
if (!$this->_writeHeader($p_filename, $v_stored_filename)) {
return false;
}
while (($v_buffer = fread($v_file, 512)) != '') {
$v_binary_data = pack("a512", "$v_buffer");
$this->_writeBlock($v_binary_data);
}
fclose($v_file);
} else {
// ----- Only header for dir
if (!$this->_writeHeader($p_filename, $v_stored_filename)) {
return false;
}
}
return true;
}
/**
* @param string $p_filename
* @param string $p_string
* @param bool $p_datetime
* @param array $p_params
* @return bool
*/
public function _addString($p_filename, $p_string, $p_datetime = false, $p_params = array())
{
$p_stamp = @$p_params["stamp"] ? $p_params["stamp"] : ($p_datetime ? $p_datetime : time());
$p_mode = @$p_params["mode"] ? $p_params["mode"] : 0600;
$p_type = @$p_params["type"] ? $p_params["type"] : "";
$p_uid = @$p_params["uid"] ? $p_params["uid"] : 0;
$p_gid = @$p_params["gid"] ? $p_params["gid"] : 0;
if (!$this->_file) {
$this->_error('Invalid file descriptor');
return false;
}
if ($p_filename == '') {
$this->_error('Invalid file name');
return false;
}
// ----- Calculate the stored filename
$p_filename = $this->_translateWinPath($p_filename, false);
// ----- If datetime is not specified, set current time
if ($p_datetime === false) {
$p_datetime = time();
}
if (!$this->_writeHeaderBlock(
$p_filename,
strlen($p_string),
$p_stamp,
$p_mode,
$p_type,
$p_uid,
$p_gid
)
) {
return false;
}
$i = 0;
while (($v_buffer = substr($p_string, (($i++) * 512), 512)) != '') {
$v_binary_data = pack("a512", $v_buffer);
$this->_writeBlock($v_binary_data);
}
return true;
}
/**
* @param string $p_filename
* @param string $p_stored_filename
* @return bool
*/
public function _writeHeader($p_filename, $p_stored_filename)
{
if ($p_stored_filename == '') {
$p_stored_filename = $p_filename;
}
$v_reduce_filename = $this->_pathReduction($p_stored_filename);
if (strlen($v_reduce_filename) > 99) {
if (!$this->_writeLongHeader($v_reduce_filename)) {
return false;
}
}
$v_info = lstat($p_filename);
$v_uid = sprintf("%07s", DecOct($v_info[4]));
$v_gid = sprintf("%07s", DecOct($v_info[5]));
$v_perms = sprintf("%07s", DecOct($v_info['mode'] & 000777));
$v_mtime = sprintf("%011s", DecOct($v_info['mtime']));
$v_linkname = '';
if (@is_link($p_filename)) {
$v_typeflag = '2';
$v_linkname = readlink($p_filename);
$v_size = sprintf("%011s", DecOct(0));
} elseif (@is_dir($p_filename)) {
$v_typeflag = "5";
$v_size = sprintf("%011s", DecOct(0));
} else {
$v_typeflag = '0';
clearstatcache();
$v_size = sprintf("%011s", DecOct($v_info['size']));
}
$v_magic = 'ustar ';
$v_version = ' ';
if (function_exists('posix_getpwuid')) {
$userinfo = posix_getpwuid($v_info[4]);
$groupinfo = posix_getgrgid($v_info[5]);
$v_uname = $userinfo['name'];
$v_gname = $groupinfo['name'];
} else {
$v_uname = '';
$v_gname = '';
}
$v_devmajor = '';
$v_devminor = '';
$v_prefix = '';
$v_binary_data_first = pack(
"a100a8a8a8a12a12",
$v_reduce_filename,
$v_perms,
$v_uid,
$v_gid,
$v_size,
$v_mtime
);
$v_binary_data_last = pack(
"a1a100a6a2a32a32a8a8a155a12",
$v_typeflag,
$v_linkname,
$v_magic,
$v_version,
$v_uname,
$v_gname,
$v_devmajor,
$v_devminor,
$v_prefix,
''
);
// ----- Calculate the checksum
$v_checksum = 0;
// ..... First part of the header
for ($i = 0; $i < 148; $i++) {
$v_checksum += ord(substr($v_binary_data_first, $i, 1));
}
// ..... Ignore the checksum value and replace it by ' ' (space)
for ($i = 148; $i < 156; $i++) {
$v_checksum += ord(' ');
}
// ..... Last part of the header
for ($i = 156, $j = 0; $i < 512; $i++, $j++) {
$v_checksum += ord(substr($v_binary_data_last, $j, 1));
}
// ----- Write the first 148 bytes of the header in the archive
$this->_writeBlock($v_binary_data_first, 148);
// ----- Write the calculated checksum
$v_checksum = sprintf("%06s ", DecOct($v_checksum));
$v_binary_data = pack("a8", $v_checksum);
$this->_writeBlock($v_binary_data, 8);
// ----- Write the last 356 bytes of the header in the archive
$this->_writeBlock($v_binary_data_last, 356);
return true;
}
/**
* @param string $p_filename
* @param int $p_size
* @param int $p_mtime
* @param int $p_perms
* @param string $p_type
* @param int $p_uid
* @param int $p_gid
* @return bool
*/
public function _writeHeaderBlock(
$p_filename,
$p_size,
$p_mtime = 0,
$p_perms = 0,
$p_type = '',
$p_uid = 0,
$p_gid = 0
) {
$p_filename = $this->_pathReduction($p_filename);
if (strlen($p_filename) > 99) {
if (!$this->_writeLongHeader($p_filename)) {
return false;
}
}
if ($p_type == "5") {
$v_size = sprintf("%011s", DecOct(0));
} else {
$v_size = sprintf("%011s", DecOct($p_size));
}
$v_uid = sprintf("%07s", DecOct($p_uid));
$v_gid = sprintf("%07s", DecOct($p_gid));
$v_perms = sprintf("%07s", DecOct($p_perms & 000777));
$v_mtime = sprintf("%11s", DecOct($p_mtime));
$v_linkname = '';
$v_magic = 'ustar ';
$v_version = ' ';
if (function_exists('posix_getpwuid')) {
$userinfo = posix_getpwuid($p_uid);
$groupinfo = posix_getgrgid($p_gid);
$v_uname = $userinfo['name'];
$v_gname = $groupinfo['name'];
} else {
$v_uname = '';
$v_gname = '';
}
$v_devmajor = '';
$v_devminor = '';
$v_prefix = '';
$v_binary_data_first = pack(
"a100a8a8a8a12A12",
$p_filename,
$v_perms,
$v_uid,
$v_gid,
$v_size,
$v_mtime
);
$v_binary_data_last = pack(
"a1a100a6a2a32a32a8a8a155a12",
$p_type,
$v_linkname,
$v_magic,
$v_version,
$v_uname,
$v_gname,
$v_devmajor,
$v_devminor,
$v_prefix,
''
);
// ----- Calculate the checksum
$v_checksum = 0;
// ..... First part of the header
for ($i = 0; $i < 148; $i++) {
$v_checksum += ord(substr($v_binary_data_first, $i, 1));
}
// ..... Ignore the checksum value and replace it by ' ' (space)
for ($i = 148; $i < 156; $i++) {
$v_checksum += ord(' ');
}
// ..... Last part of the header
for ($i = 156, $j = 0; $i < 512; $i++, $j++) {
$v_checksum += ord(substr($v_binary_data_last, $j, 1));
}
// ----- Write the first 148 bytes of the header in the archive
$this->_writeBlock($v_binary_data_first, 148);
// ----- Write the calculated checksum
$v_checksum = sprintf("%06s ", DecOct($v_checksum));
$v_binary_data = pack("a8", $v_checksum);
$this->_writeBlock($v_binary_data, 8);
// ----- Write the last 356 bytes of the header in the archive
$this->_writeBlock($v_binary_data_last, 356);
return true;
}
/**
* @param string $p_filename
* @return bool
*/
public function _writeLongHeader($p_filename)
{
$v_size = sprintf("%11s ", DecOct(strlen($p_filename)));
$v_typeflag = 'L';
$v_linkname = '';
$v_magic = '';
$v_version = '';
$v_uname = '';
$v_gname = '';
$v_devmajor = '';
$v_devminor = '';
$v_prefix = '';
$v_binary_data_first = pack(
"a100a8a8a8a12a12",
'././@LongLink',
0,
0,
0,
$v_size,
0
);
$v_binary_data_last = pack(
"a1a100a6a2a32a32a8a8a155a12",
$v_typeflag,
$v_linkname,
$v_magic,
$v_version,
$v_uname,
$v_gname,
$v_devmajor,
$v_devminor,
$v_prefix,
''
);
// ----- Calculate the checksum
$v_checksum = 0;
// ..... First part of the header
for ($i = 0; $i < 148; $i++) {
$v_checksum += ord(substr($v_binary_data_first, $i, 1));
}
// ..... Ignore the checksum value and replace it by ' ' (space)
for ($i = 148; $i < 156; $i++) {
$v_checksum += ord(' ');
}
// ..... Last part of the header
for ($i = 156, $j = 0; $i < 512; $i++, $j++) {
$v_checksum += ord(substr($v_binary_data_last, $j, 1));
}
// ----- Write the first 148 bytes of the header in the archive
$this->_writeBlock($v_binary_data_first, 148);
// ----- Write the calculated checksum
$v_checksum = sprintf("%06s ", DecOct($v_checksum));
$v_binary_data = pack("a8", $v_checksum);
$this->_writeBlock($v_binary_data, 8);
// ----- Write the last 356 bytes of the header in the archive
$this->_writeBlock($v_binary_data_last, 356);
// ----- Write the filename as content of the block
$i = 0;
while (($v_buffer = substr($p_filename, (($i++) * 512), 512)) != '') {
$v_binary_data = pack("a512", "$v_buffer");
$this->_writeBlock($v_binary_data);
}
return true;
}
/**
* @param mixed $v_binary_data
* @param mixed $v_header
* @return bool
*/
public function _readHeader($v_binary_data, &$v_header)
{
if (strlen($v_binary_data) == 0) {
$v_header['filename'] = '';
return true;
}
if (strlen($v_binary_data) != 512) {
$v_header['filename'] = '';
$this->_error('Invalid block size : ' . strlen($v_binary_data));
return false;
}
if (!is_array($v_header)) {
$v_header = array();
}
// ----- Calculate the checksum
$v_checksum = 0;
// ..... First part of the header
$v_binary_split = str_split($v_binary_data);
$v_checksum += array_sum(array_map('ord', array_slice($v_binary_split, 0, 148)));
$v_checksum += array_sum(array_map('ord', array(' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',)));
$v_checksum += array_sum(array_map('ord', array_slice($v_binary_split, 156, 512)));
$v_data = unpack($this->_fmt, $v_binary_data);
if (strlen($v_data["prefix"]) > 0) {
$v_data["filename"] = "$v_data[prefix]/$v_data[filename]";
}
// ----- Extract the checksum
$v_header['checksum'] = OctDec(trim($v_data['checksum']));
if ($v_header['checksum'] != $v_checksum) {
$v_header['filename'] = '';
// ----- Look for last block (empty block)
if (($v_checksum == 256) && ($v_header['checksum'] == 0)) {
return true;
}
$this->_error(
'Invalid checksum for file "' . $v_data['filename']
. '" : ' . $v_checksum . ' calculated, '
. $v_header['checksum'] . ' expected'
);
return false;
}
// ----- Extract the properties
$v_header['filename'] = rtrim($v_data['filename'], "\0");
if ($this->_maliciousFilename($v_header['filename'])) {
$this->_error(
'Malicious .tar detected, file "' . $v_header['filename'] .
'" will not install in desired directory tree'
);
return false;
}
$v_header['mode'] = OctDec(trim($v_data['mode']));
$v_header['uid'] = OctDec(trim($v_data['uid']));
$v_header['gid'] = OctDec(trim($v_data['gid']));
$v_header['size'] = $this->_tarRecToSize($v_data['size']);
$v_header['mtime'] = OctDec(trim($v_data['mtime']));
if (($v_header['typeflag'] = $v_data['typeflag']) == "5") {
$v_header['size'] = 0;
}
$v_header['link'] = trim($v_data['link']);
/* ----- All these fields are removed form the header because
they do not carry interesting info
$v_header[magic] = trim($v_data[magic]);
$v_header[version] = trim($v_data[version]);
$v_header[uname] = trim($v_data[uname]);
$v_header[gname] = trim($v_data[gname]);
$v_header[devmajor] = trim($v_data[devmajor]);
$v_header[devminor] = trim($v_data[devminor]);
*/
return true;
}
/**
* Convert Tar record size to actual size
*
* @param string $tar_size
* @return size of tar record in bytes
*/
private function _tarRecToSize($tar_size)
{
/*
* First byte of size has a special meaning if bit 7 is set.
*
* Bit 7 indicates base-256 encoding if set.
* Bit 6 is the sign bit.
* Bits 5:0 are most significant value bits.
*/
$ch = ord($tar_size[0]);
if ($ch & 0x80) {
// Full 12-bytes record is required.
$rec_str = $tar_size . "\x00";
$size = ($ch & 0x40) ? -1 : 0;
$size = ($size << 6) | ($ch & 0x3f);
for ($num_ch = 1; $num_ch < 12; ++$num_ch) {
$size = ($size * 256) + ord($rec_str[$num_ch]);
}
return $size;
} else {
return OctDec(trim($tar_size));
}
}
/**
* Detect and report a malicious file name
*
* @param string $file
*
* @return bool
*/
private function _maliciousFilename($file)
{
if (strpos($file, '/../') !== false) {
return true;
}
if (strpos($file, '../') === 0) {
return true;
}
return false;
}
/**
* @param $v_header
* @return bool
*/
public function _readLongHeader(&$v_header)
{
$v_filename = '';
$v_filesize = $v_header['size'];
$n = floor($v_header['size'] / 512);
for ($i = 0; $i < $n; $i++) {
$v_content = $this->_readBlock();
$v_filename .= $v_content;
}
if (($v_header['size'] % 512) != 0) {
$v_content = $this->_readBlock();
$v_filename .= $v_content;
}
// ----- Read the next header
$v_binary_data = $this->_readBlock();
if (!$this->_readHeader($v_binary_data, $v_header)) {
return false;
}
$v_filename = rtrim(substr($v_filename, 0, $v_filesize), "\0");
$v_header['filename'] = $v_filename;
if ($this->_maliciousFilename($v_filename)) {
$this->_error(
'Malicious .tar detected, file "' . $v_filename .
'" will not install in desired directory tree'
);
return false;
}
return true;
}
/**
* This method extract from the archive one file identified by $p_filename.
* The return value is a string with the file content, or null on error.
*
* @param string $p_filename The path of the file to extract in a string.
*
* @return a string with the file content or null.
*/
private function _extractInString($p_filename)
{
$v_result_str = "";
while (strlen($v_binary_data = $this->_readBlock()) != 0) {
if (!$this->_readHeader($v_binary_data, $v_header)) {
return null;
}
if ($v_header['filename'] == '') {
continue;
}
// ----- Look for long filename
if ($v_header['typeflag'] == 'L') {
if (!$this->_readLongHeader($v_header)) {
return null;
}
}
if ($v_header['filename'] == $p_filename) {
if ($v_header['typeflag'] == "5") {
$this->_error(
'Unable to extract in string a directory '
. 'entry {' . $v_header['filename'] . '}'
);
return null;
} else {
$n = floor($v_header['size'] / 512);
for ($i = 0; $i < $n; $i++) {
$v_result_str .= $this->_readBlock();
}
if (($v_header['size'] % 512) != 0) {
$v_content = $this->_readBlock();
$v_result_str .= substr(
$v_content,
0,
($v_header['size'] % 512)
);
}
return $v_result_str;
}
} else {
$this->_jumpBlock(ceil(($v_header['size'] / 512)));
}
}
return null;
}
/**
* @param string $p_path
* @param string $p_list_detail
* @param string $p_mode
* @param string $p_file_list
* @param string $p_remove_path
* @param bool $p_preserve
* @return bool
*/
public function _extractList(
$p_path,
&$p_list_detail,
$p_mode,
$p_file_list,
$p_remove_path,
$p_preserve = false
) {
$v_result = true;
$v_nb = 0;
$v_extract_all = true;
$v_listing = false;
$p_path = $this->_translateWinPath($p_path, false);
if ($p_path == '' || (substr($p_path, 0, 1) != '/'
&& substr($p_path, 0, 3) != "../" && !strpos($p_path, ':'))
) {
$p_path = "./" . $p_path;
}
$p_remove_path = $this->_translateWinPath($p_remove_path);
// ----- Look for path to remove format (should end by /)
if (($p_remove_path != '') && (substr($p_remove_path, -1) != '/')) {
$p_remove_path .= '/';
}
$p_remove_path_size = strlen($p_remove_path);
switch ($p_mode) {
case "complete" :
$v_extract_all = true;
$v_listing = false;
break;
case "partial" :
$v_extract_all = false;
$v_listing = false;
break;
case "list" :
$v_extract_all = false;
$v_listing = true;
break;
default :
$this->_error('Invalid extract mode (' . $p_mode . ')');
return false;
}
clearstatcache();
while (strlen($v_binary_data = $this->_readBlock()) != 0) {
$v_extract_file = false;
$v_extraction_stopped = 0;
if (!$this->_readHeader($v_binary_data, $v_header)) {
return false;
}
if ($v_header['filename'] == '') {
continue;
}
// ----- Look for long filename
if ($v_header['typeflag'] == 'L') {
if (!$this->_readLongHeader($v_header)) {
return false;
}
}
// ignore extended / pax headers
if ($v_header['typeflag'] == 'x' || $v_header['typeflag'] == 'g') {
$this->_jumpBlock(ceil(($v_header['size'] / 512)));
continue;
}
if ((!$v_extract_all) && (is_array($p_file_list))) {
// ----- By default no unzip if the file is not found
$v_extract_file = false;
for ($i = 0; $i < sizeof($p_file_list); $i++) {
// ----- Look if it is a directory
if (substr($p_file_list[$i], -1) == '/') {
// ----- Look if the directory is in the filename path
if ((strlen($v_header['filename']) > strlen($p_file_list[$i]))
&& (substr($v_header['filename'], 0, strlen($p_file_list[$i]))
== $p_file_list[$i])
) {
$v_extract_file = true;
break;
}
} // ----- It is a file, so compare the file names
elseif ($p_file_list[$i] == $v_header['filename']) {
$v_extract_file = true;
break;
}
}
} else {
$v_extract_file = true;
}
// ----- Look if this file need to be extracted
if (($v_extract_file) && (!$v_listing)) {
if (($p_remove_path != '')
&& (substr($v_header['filename'] . '/', 0, $p_remove_path_size)
== $p_remove_path)
) {
$v_header['filename'] = substr(
$v_header['filename'],
$p_remove_path_size
);
if ($v_header['filename'] == '') {
continue;
}
}
if (($p_path != './') && ($p_path != '/')) {
while (substr($p_path, -1) == '/') {
$p_path = substr($p_path, 0, strlen($p_path) - 1);
}
if (substr($v_header['filename'], 0, 1) == '/') {
$v_header['filename'] = $p_path . $v_header['filename'];
} else {
$v_header['filename'] = $p_path . '/' . $v_header['filename'];
}
}
if (file_exists($v_header['filename'])) {
if ((@is_dir($v_header['filename']))
&& ($v_header['typeflag'] == '')
) {
$this->_error(
'File ' . $v_header['filename']
. ' already exists as a directory'
);
return false;
}
if (($this->_isArchive($v_header['filename']))
&& ($v_header['typeflag'] == "5")
) {
$this->_error(
'Directory ' . $v_header['filename']
. ' already exists as a file'
);
return false;
}
if (!is_writeable($v_header['filename'])) {
$this->_error(
'File ' . $v_header['filename']
. ' already exists and is write protected'
);
return false;
}
if (filemtime($v_header['filename']) > $v_header['mtime']) {
// To be completed : An error or silent no replace ?
}
} // ----- Check the directory availability and create it if necessary
elseif (($v_result
= $this->_dirCheck(
($v_header['typeflag'] == "5"
? $v_header['filename']
: dirname($v_header['filename']))
)) != 1
) {
$this->_error('Unable to create path for ' . $v_header['filename']);
return false;
}
if ($v_extract_file) {
if ($v_header['typeflag'] == "5") {
if (!@file_exists($v_header['filename'])) {
if (!@mkdir($v_header['filename'], 0777)) {
$this->_error(
'Unable to create directory {'
. $v_header['filename'] . '}'
);
return false;
}
}
} elseif ($v_header['typeflag'] == "2") {
if (@file_exists($v_header['filename'])) {
@unlink($v_header['filename']);
}
if (!@symlink($v_header['link'], $v_header['filename'])) {
$this->_error(
'Unable to extract symbolic link {'
. $v_header['filename'] . '}'
);
return false;
}
} else {
if (($v_dest_file = @fopen($v_header['filename'], "wb")) == 0) {
$this->_error(
'Error while opening {' . $v_header['filename']
. '} in write binary mode'
);
return false;
} else {
$n = floor($v_header['size'] / 512);
for ($i = 0; $i < $n; $i++) {
$v_content = $this->_readBlock();
fwrite($v_dest_file, $v_content, 512);
}
if (($v_header['size'] % 512) != 0) {
$v_content = $this->_readBlock();
fwrite($v_dest_file, $v_content, ($v_header['size'] % 512));
}
@fclose($v_dest_file);
if ($p_preserve) {
@chown($v_header['filename'], $v_header['uid']);
@chgrp($v_header['filename'], $v_header['gid']);
}
// ----- Change the file mode, mtime
@touch($v_header['filename'], $v_header['mtime']);
if ($v_header['mode'] & 0111) {
// make file executable, obey umask
$mode = fileperms($v_header['filename']) | (~umask() & 0111);
@chmod($v_header['filename'], $mode);
}
}
// ----- Check the file size
clearstatcache();
if (!is_file($v_header['filename'])) {
$this->_error(
'Extracted file ' . $v_header['filename']
. 'does not exist. Archive may be corrupted.'
);
return false;
}
$filesize = filesize($v_header['filename']);
if ($filesize != $v_header['size']) {
$this->_error(
'Extracted file ' . $v_header['filename']
. ' does not have the correct file size \''
. $filesize
. '\' (' . $v_header['size']
. ' expected). Archive may be corrupted.'
);
return false;
}
}
} else {
$this->_jumpBlock(ceil(($v_header['size'] / 512)));
}
} else {
$this->_jumpBlock(ceil(($v_header['size'] / 512)));
}
/* TBC : Seems to be unused ...
if ($this->_compress)
$v_end_of_file = @gzeof($this->_file);
else
$v_end_of_file = @feof($this->_file);
*/
if ($v_listing || $v_extract_file || $v_extraction_stopped) {
// ----- Log extracted files
if (($v_file_dir = dirname($v_header['filename']))
== $v_header['filename']
) {
$v_file_dir = '';
}
if ((substr($v_header['filename'], 0, 1) == '/') && ($v_file_dir == '')) {
$v_file_dir = '/';
}
$p_list_detail[$v_nb++] = $v_header;
if (is_array($p_file_list) && (count($p_list_detail) == count($p_file_list))) {
return true;
}
}
}
return true;
}
/**
* @return bool
*/
public function _openAppend()
{
if (filesize($this->_tarname) == 0) {
return $this->_openWrite();
}
if ($this->_compress) {
$this->_close();
if (!@rename($this->_tarname, $this->_tarname . ".tmp")) {
$this->_error(
'Error while renaming \'' . $this->_tarname
. '\' to temporary file \'' . $this->_tarname
. '.tmp\''
);
return false;
}
if ($this->_compress_type == 'gz') {
$v_temp_tar = @gzopen($this->_tarname . ".tmp", "rb");
} elseif ($this->_compress_type == 'bz2') {
$v_temp_tar = @bzopen($this->_tarname . ".tmp", "r");
} elseif ($this->_compress_type == 'lzma2') {
$v_temp_tar = @xzopen($this->_tarname . ".tmp", "r");
}
if ($v_temp_tar == 0) {
$this->_error(
'Unable to open file \'' . $this->_tarname
. '.tmp\' in binary read mode'
);
@rename($this->_tarname . ".tmp", $this->_tarname);
return false;
}
if (!$this->_openWrite()) {
@rename($this->_tarname . ".tmp", $this->_tarname);
return false;
}
if ($this->_compress_type == 'gz') {
$end_blocks = 0;
while (!@gzeof($v_temp_tar)) {
$v_buffer = @gzread($v_temp_tar, 512);
if ($v_buffer == ARCHIVE_TAR_END_BLOCK || strlen($v_buffer) == 0) {
$end_blocks++;
// do not copy end blocks, we will re-make them
// after appending
continue;
} elseif ($end_blocks > 0) {
for ($i = 0; $i < $end_blocks; $i++) {
$this->_writeBlock(ARCHIVE_TAR_END_BLOCK);
}
$end_blocks = 0;
}
$v_binary_data = pack("a512", $v_buffer);
$this->_writeBlock($v_binary_data);
}
@gzclose($v_temp_tar);
} elseif ($this->_compress_type == 'bz2') {
$end_blocks = 0;
while (strlen($v_buffer = @bzread($v_temp_tar, 512)) > 0) {
if ($v_buffer == ARCHIVE_TAR_END_BLOCK || strlen($v_buffer) == 0) {
$end_blocks++;
// do not copy end blocks, we will re-make them
// after appending
continue;
} elseif ($end_blocks > 0) {
for ($i = 0; $i < $end_blocks; $i++) {
$this->_writeBlock(ARCHIVE_TAR_END_BLOCK);
}
$end_blocks = 0;
}
$v_binary_data = pack("a512", $v_buffer);
$this->_writeBlock($v_binary_data);
}
@bzclose($v_temp_tar);
} elseif ($this->_compress_type == 'lzma2') {
$end_blocks = 0;
while (strlen($v_buffer = @xzread($v_temp_tar, 512)) > 0) {
if ($v_buffer == ARCHIVE_TAR_END_BLOCK || strlen($v_buffer) == 0) {
$end_blocks++;
// do not copy end blocks, we will re-make them
// after appending
continue;
} elseif ($end_blocks > 0) {
for ($i = 0; $i < $end_blocks; $i++) {
$this->_writeBlock(ARCHIVE_TAR_END_BLOCK);
}
$end_blocks = 0;
}
$v_binary_data = pack("a512", $v_buffer);
$this->_writeBlock($v_binary_data);
}
@xzclose($v_temp_tar);
}
if (!@unlink($this->_tarname . ".tmp")) {
$this->_error(
'Error while deleting temporary file \''
. $this->_tarname . '.tmp\''
);
}
} else {
// ----- For not compressed tar, just add files before the last
// one or two 512 bytes block
if (!$this->_openReadWrite()) {
return false;
}
clearstatcache();
$v_size = filesize($this->_tarname);
// We might have zero, one or two end blocks.
// The standard is two, but we should try to handle
// other cases.
fseek($this->_file, $v_size - 1024);
if (fread($this->_file, 512) == ARCHIVE_TAR_END_BLOCK) {
fseek($this->_file, $v_size - 1024);
} elseif (fread($this->_file, 512) == ARCHIVE_TAR_END_BLOCK) {
fseek($this->_file, $v_size - 512);
}
}
return true;
}
/**
* @param $p_filelist
* @param string $p_add_dir
* @param string $p_remove_dir
* @return bool
*/
public function _append($p_filelist, $p_add_dir = '', $p_remove_dir = '')
{
if (!$this->_openAppend()) {
return false;
}
if ($this->_addList($p_filelist, $p_add_dir, $p_remove_dir)) {
$this->_writeFooter();
}
$this->_close();
return true;
}
/**
* Check if a directory exists and create it (including parent
* dirs) if not.
*
* @param string $p_dir directory to check
*
* @return bool true if the directory exists or was created
*/
public function _dirCheck($p_dir)
{
clearstatcache();
if ((@is_dir($p_dir)) || ($p_dir == '')) {
return true;
}
$p_parent_dir = dirname($p_dir);
if (($p_parent_dir != $p_dir) &&
($p_parent_dir != '') &&
(!$this->_dirCheck($p_parent_dir))
) {
return false;
}
if (!@mkdir($p_dir, 0777)) {
$this->_error("Unable to create directory '$p_dir'");
return false;
}
return true;
}
/**
* Compress path by changing for example "/dir/foo/../bar" to "/dir/bar",
* rand emove double slashes.
*
* @param string $p_dir path to reduce
*
* @return string reduced path
*/
private function _pathReduction($p_dir)
{
$v_result = '';
// ----- Look for not empty path
if ($p_dir != '') {
// ----- Explode path by directory names
$v_list = explode('/', $p_dir);
// ----- Study directories from last to first
for ($i = sizeof($v_list) - 1; $i >= 0; $i--) {
// ----- Look for current path
if ($v_list[$i] == ".") {
// ----- Ignore this directory
// Should be the first $i=0, but no check is done
} else {
if ($v_list[$i] == "..") {
// ----- Ignore it and ignore the $i-1
$i--;
} else {
if (($v_list[$i] == '')
&& ($i != (sizeof($v_list) - 1))
&& ($i != 0)
) {
// ----- Ignore only the double '//' in path,
// but not the first and last /
} else {
$v_result = $v_list[$i] . ($i != (sizeof($v_list) - 1) ? '/'
. $v_result : '');
}
}
}
}
}
if (defined('OS_WINDOWS') && OS_WINDOWS) {
$v_result = strtr($v_result, '\\', '/');
}
return $v_result;
}
/**
* @param $p_path
* @param bool $p_remove_disk_letter
* @return string
*/
public function _translateWinPath($p_path, $p_remove_disk_letter = true)
{
if (defined('OS_WINDOWS') && OS_WINDOWS) {
// ----- Look for potential disk letter
if (($p_remove_disk_letter)
&& (($v_position = strpos($p_path, ':')) != false)
) {
$p_path = substr($p_path, $v_position + 1);
}
// ----- Change potential windows directory separator
if ((strpos($p_path, '\\') > 0) || (substr($p_path, 0, 1) == '\\')) {
$p_path = strtr($p_path, '\\', '/');
}
}
return $p_path;
}
}
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4: */
/**
* PHP Version 5
*
* Copyright (c) 1997-2004 The PHP Group
*
* This source file is subject to version 3.0 of the PHP license,
* that is bundled with this package in the file LICENSE, and is
* available through the world-wide-web at the following url:
* http://www.php.net/license/3_0.txt.
* If you did not receive a copy of the PHP license and are unable to
* obtain it through the world-wide-web, please send a note to
* license@php.net so we can mail you a copy immediately.
*
* @category Console
* @package Console_Getopt
* @author Andrei Zmievski <andrei@php.net>
* @license http://www.php.net/license/3_0.txt PHP 3.0
* @version CVS: $Id$
* @link http://pear.php.net/package/Console_Getopt
*/
require_once 'PEAR.php';
/**
* Command-line options parsing class.
*
* @category Console
* @package Console_Getopt
* @author Andrei Zmievski <andrei@php.net>
* @license http://www.php.net/license/3_0.txt PHP 3.0
* @link http://pear.php.net/package/Console_Getopt
*/
class Console_Getopt
{
/**
* Parses the command-line options.
*
* The first parameter to this function should be the list of command-line
* arguments without the leading reference to the running program.
*
* The second parameter is a string of allowed short options. Each of the
* option letters can be followed by a colon ':' to specify that the option
* requires an argument, or a double colon '::' to specify that the option
* takes an optional argument.
*
* The third argument is an optional array of allowed long options. The
* leading '--' should not be included in the option name. Options that
* require an argument should be followed by '=', and options that take an
* option argument should be followed by '=='.
*
* The return value is an array of two elements: the list of parsed
* options and the list of non-option command-line arguments. Each entry in
* the list of parsed options is a pair of elements - the first one
* specifies the option, and the second one specifies the option argument,
* if there was one.
*
* Long and short options can be mixed.
*
* Most of the semantics of this function are based on GNU getopt_long().
*
* @param array $args an array of command-line arguments
* @param string $short_options specifies the list of allowed short options
* @param array $long_options specifies the list of allowed long options
* @param boolean $skip_unknown suppresses Console_Getopt: unrecognized option
*
* @return array two-element array containing the list of parsed options and
* the non-option arguments
*/
public static function getopt2($args, $short_options, $long_options = null, $skip_unknown = false)
{
return Console_Getopt::doGetopt(2, $args, $short_options, $long_options, $skip_unknown);
}
/**
* This function expects $args to start with the script name (POSIX-style).
* Preserved for backwards compatibility.
*
* @param array $args an array of command-line arguments
* @param string $short_options specifies the list of allowed short options
* @param array $long_options specifies the list of allowed long options
*
* @see getopt2()
* @return array two-element array containing the list of parsed options and
* the non-option arguments
*/
public static function getopt($args, $short_options, $long_options = null, $skip_unknown = false)
{
return Console_Getopt::doGetopt(1, $args, $short_options, $long_options, $skip_unknown);
}
/**
* The actual implementation of the argument parsing code.
*
* @param int $version Version to use
* @param array $args an array of command-line arguments
* @param string $short_options specifies the list of allowed short options
* @param array $long_options specifies the list of allowed long options
* @param boolean $skip_unknown suppresses Console_Getopt: unrecognized option
*
* @return array
*/
public static function doGetopt($version, $args, $short_options, $long_options = null, $skip_unknown = false)
{
// in case you pass directly readPHPArgv() as the first arg
if (PEAR::isError($args)) {
return $args;
}
if (empty($args)) {
return array(array(), array());
}
$non_opts = $opts = array();
settype($args, 'array');
if ($long_options) {
sort($long_options);
}
/*
* Preserve backwards compatibility with callers that relied on
* erroneous POSIX fix.
*/
if ($version < 2) {
if (isset($args[0]{0}) && $args[0]{0} != '-') {
array_shift($args);
}
}
reset($args);
while (list($i, $arg) = each($args)) {
/* The special element '--' means explicit end of
options. Treat the rest of the arguments as non-options
and end the loop. */
if ($arg == '--') {
$non_opts = array_merge($non_opts, array_slice($args, $i + 1));
break;
}
if ($arg{0} != '-' || (strlen($arg) > 1 && $arg{1} == '-' && !$long_options)) {
$non_opts = array_merge($non_opts, array_slice($args, $i));
break;
} elseif (strlen($arg) > 1 && $arg{1} == '-') {
$error = Console_Getopt::_parseLongOption(substr($arg, 2),
$long_options,
$opts,
$args,
$skip_unknown);
if (PEAR::isError($error)) {
return $error;
}
} elseif ($arg == '-') {
// - is stdin
$non_opts = array_merge($non_opts, array_slice($args, $i));
break;
} else {
$error = Console_Getopt::_parseShortOption(substr($arg, 1),
$short_options,
$opts,
$args,
$skip_unknown);
if (PEAR::isError($error)) {
return $error;
}
}
}
return array($opts, $non_opts);
}
/**
* Parse short option
*
* @param string $arg Argument
* @param string[] $short_options Available short options
* @param string[][] &$opts
* @param string[] &$args
* @param boolean $skip_unknown suppresses Console_Getopt: unrecognized option
*
* @return void
*/
protected static function _parseShortOption($arg, $short_options, &$opts, &$args, $skip_unknown)
{
for ($i = 0; $i < strlen($arg); $i++) {
$opt = $arg{$i};
$opt_arg = null;
/* Try to find the short option in the specifier string. */
if (($spec = strstr($short_options, $opt)) === false || $arg{$i} == ':') {
if ($skip_unknown === true) {
break;
}
$msg = "Console_Getopt: unrecognized option -- $opt";
return PEAR::raiseError($msg);
}
if (strlen($spec) > 1 && $spec{1} == ':') {
if (strlen($spec) > 2 && $spec{2} == ':') {
if ($i + 1 < strlen($arg)) {
/* Option takes an optional argument. Use the remainder of
the arg string if there is anything left. */
$opts[] = array($opt, substr($arg, $i + 1));
break;
}
} else {
/* Option requires an argument. Use the remainder of the arg
string if there is anything left. */
if ($i + 1 < strlen($arg)) {
$opts[] = array($opt, substr($arg, $i + 1));
break;
} else if (list(, $opt_arg) = each($args)) {
/* Else use the next argument. */;
if (Console_Getopt::_isShortOpt($opt_arg)
|| Console_Getopt::_isLongOpt($opt_arg)) {
$msg = "option requires an argument --$opt";
return PEAR::raiseError("Console_Getopt: " . $msg);
}
} else {
$msg = "option requires an argument --$opt";
return PEAR::raiseError("Console_Getopt: " . $msg);
}
}
}
$opts[] = array($opt, $opt_arg);
}
}
/**
* Checks if an argument is a short option
*
* @param string $arg Argument to check
*
* @return bool
*/
protected static function _isShortOpt($arg)
{
return strlen($arg) == 2 && $arg[0] == '-'
&& preg_match('/[a-zA-Z]/', $arg[1]);
}
/**
* Checks if an argument is a long option
*
* @param string $arg Argument to check
*
* @return bool
*/
protected static function _isLongOpt($arg)
{
return strlen($arg) > 2 && $arg[0] == '-' && $arg[1] == '-' &&
preg_match('/[a-zA-Z]+$/', substr($arg, 2));
}
/**
* Parse long option
*
* @param string $arg Argument
* @param string[] $long_options Available long options
* @param string[][] &$opts
* @param string[] &$args
*
* @return void|PEAR_Error
*/
protected static function _parseLongOption($arg, $long_options, &$opts, &$args, $skip_unknown)
{
@list($opt, $opt_arg) = explode('=', $arg, 2);
$opt_len = strlen($opt);
for ($i = 0; $i < count($long_options); $i++) {
$long_opt = $long_options[$i];
$opt_start = substr($long_opt, 0, $opt_len);
$long_opt_name = str_replace('=', '', $long_opt);
/* Option doesn't match. Go on to the next one. */
if ($long_opt_name != $opt) {
continue;
}
$opt_rest = substr($long_opt, $opt_len);
/* Check that the options uniquely matches one of the allowed
options. */
if ($i + 1 < count($long_options)) {
$next_option_rest = substr($long_options[$i + 1], $opt_len);
} else {
$next_option_rest = '';
}
if ($opt_rest != '' && $opt{0} != '=' &&
$i + 1 < count($long_options) &&
$opt == substr($long_options[$i+1], 0, $opt_len) &&
$next_option_rest != '' &&
$next_option_rest{0} != '=') {
$msg = "Console_Getopt: option --$opt is ambiguous";
return PEAR::raiseError($msg);
}
if (substr($long_opt, -1) == '=') {
if (substr($long_opt, -2) != '==') {
/* Long option requires an argument.
Take the next argument if one wasn't specified. */;
if (!strlen($opt_arg) && !(list(, $opt_arg) = each($args))) {
$msg = "Console_Getopt: option requires an argument --$opt";
return PEAR::raiseError($msg);
}
if (Console_Getopt::_isShortOpt($opt_arg)
|| Console_Getopt::_isLongOpt($opt_arg)) {
$msg = "Console_Getopt: option requires an argument --$opt";
return PEAR::raiseError($msg);
}
}
} else if ($opt_arg) {
$msg = "Console_Getopt: option --$opt doesn't allow an argument";
return PEAR::raiseError($msg);
}
$opts[] = array('--' . $opt, $opt_arg);
return;
}
if ($skip_unknown === true) {
return;
}
return PEAR::raiseError("Console_Getopt: unrecognized option --$opt");
}
/**
* Safely read the $argv PHP array across different PHP configurations.
* Will take care on register_globals and register_argc_argv ini directives
*
* @return mixed the $argv PHP array or PEAR error if not registered
*/
public static function readPHPArgv()
{
global $argv;
if (!is_array($argv)) {
if (!@is_array($_SERVER['argv'])) {
if (!@is_array($GLOBALS['HTTP_SERVER_VARS']['argv'])) {
$msg = "Could not read cmd args (register_argc_argv=Off?)";
return PEAR::raiseError("Console_Getopt: " . $msg);
}
return $GLOBALS['HTTP_SERVER_VARS']['argv'];
}
return $_SERVER['argv'];
}
return $argv;
}
}<?php
/**
* The OS_Guess class
*
* PHP versions 4 and 5
*
* @category pear
* @package PEAR
* @author Stig Bakken <ssb@php.net>
* @author Gregory Beaver <cellog@php.net>
* @copyright 1997-2009 The Authors
* @license http://opensource.org/licenses/bsd-license.php New BSD License
* @link http://pear.php.net/package/PEAR
* @since File available since PEAR 0.1
*/
// {{{ uname examples
// php_uname() without args returns the same as 'uname -a', or a PHP-custom
// string for Windows.
// PHP versions prior to 4.3 return the uname of the host where PHP was built,
// as of 4.3 it returns the uname of the host running the PHP code.
//
// PC RedHat Linux 7.1:
// Linux host.example.com 2.4.2-2 #1 Sun Apr 8 20:41:30 EDT 2001 i686 unknown
//
// PC Debian Potato:
// Linux host 2.4.17 #2 SMP Tue Feb 12 15:10:04 CET 2002 i686 unknown
//
// PC FreeBSD 3.3:
// FreeBSD host.example.com 3.3-STABLE FreeBSD 3.3-STABLE #0: Mon Feb 21 00:42:31 CET 2000 root@example.com:/usr/src/sys/compile/CONFIG i386
//
// PC FreeBSD 4.3:
// FreeBSD host.example.com 4.3-RELEASE FreeBSD 4.3-RELEASE #1: Mon Jun 25 11:19:43 EDT 2001 root@example.com:/usr/src/sys/compile/CONFIG i386
//
// PC FreeBSD 4.5:
// FreeBSD host.example.com 4.5-STABLE FreeBSD 4.5-STABLE #0: Wed Feb 6 23:59:23 CET 2002 root@example.com:/usr/src/sys/compile/CONFIG i386
//
// PC FreeBSD 4.5 w/uname from GNU shellutils:
// FreeBSD host.example.com 4.5-STABLE FreeBSD 4.5-STABLE #0: Wed Feb i386 unknown
//
// HP 9000/712 HP-UX 10:
// HP-UX iq B.10.10 A 9000/712 2008429113 two-user license
//
// HP 9000/712 HP-UX 10 w/uname from GNU shellutils:
// HP-UX host B.10.10 A 9000/712 unknown
//
// IBM RS6000/550 AIX 4.3:
// AIX host 3 4 000003531C00
//
// AIX 4.3 w/uname from GNU shellutils:
// AIX host 3 4 000003531C00 unknown
//
// SGI Onyx IRIX 6.5 w/uname from GNU shellutils:
// IRIX64 host 6.5 01091820 IP19 mips
//
// SGI Onyx IRIX 6.5:
// IRIX64 host 6.5 01091820 IP19
//
// SparcStation 20 Solaris 8 w/uname from GNU shellutils:
// SunOS host.example.com 5.8 Generic_108528-12 sun4m sparc
//
// SparcStation 20 Solaris 8:
// SunOS host.example.com 5.8 Generic_108528-12 sun4m sparc SUNW,SPARCstation-20
//
// Mac OS X (Darwin)
// Darwin home-eden.local 7.5.0 Darwin Kernel Version 7.5.0: Thu Aug 5 19:26:16 PDT 2004; root:xnu/xnu-517.7.21.obj~3/RELEASE_PPC Power Macintosh
//
// Mac OS X early versions
//
// }}}
/* TODO:
* - define endianness, to allow matchSignature("bigend") etc.
*/
/**
* Retrieves information about the current operating system
*
* This class uses php_uname() to grok information about the current OS
*
* @category pear
* @package PEAR
* @author Stig Bakken <ssb@php.net>
* @author Gregory Beaver <cellog@php.net>
* @copyright 1997-2009 The Authors
* @license http://opensource.org/licenses/bsd-license.php New BSD License
* @version Release: @package_version@
* @link http://pear.php.net/package/PEAR
* @since Class available since Release 0.1
*/
class OS_Guess
{
var $sysname;
var $nodename;
var $cpu;
var $release;
var $extra;
function __construct($uname = null)
{
list($this->sysname,
$this->release,
$this->cpu,
$this->extra,
$this->nodename) = $this->parseSignature($uname);
}
function parseSignature($uname = null)
{
static $sysmap = array(
'HP-UX' => 'hpux',
'IRIX64' => 'irix',
);
static $cpumap = array(
'i586' => 'i386',
'i686' => 'i386',
'ppc' => 'powerpc',
);
if ($uname === null) {
$uname = php_uname();
}
$parts = preg_split('/\s+/', trim($uname));
$n = count($parts);
$release = $machine = $cpu = '';
$sysname = $parts[0];
$nodename = $parts[1];
$cpu = $parts[$n-1];
$extra = '';
if ($cpu == 'unknown') {
$cpu = $parts[$n - 2];
}
switch ($sysname) {
case 'AIX' :
$release = "$parts[3].$parts[2]";
break;
case 'Windows' :
switch ($parts[1]) {
case '95/98':
$release = '9x';
break;
default:
$release = $parts[1];
break;
}
$cpu = 'i386';
break;
case 'Linux' :
$extra = $this->_detectGlibcVersion();
// use only the first two digits from the kernel version
$release = preg_replace('/^([0-9]+\.[0-9]+).*/', '\1', $parts[2]);
break;
case 'Mac' :
$sysname = 'darwin';
$nodename = $parts[2];
$release = $parts[3];
if ($cpu == 'Macintosh') {
if ($parts[$n - 2] == 'Power') {
$cpu = 'powerpc';
}
}
break;
case 'Darwin' :
if ($cpu == 'Macintosh') {
if ($parts[$n - 2] == 'Power') {
$cpu = 'powerpc';
}
}
$release = preg_replace('/^([0-9]+\.[0-9]+).*/', '\1', $parts[2]);
break;
default:
$release = preg_replace('/-.*/', '', $parts[2]);
break;
}
if (isset($sysmap[$sysname])) {
$sysname = $sysmap[$sysname];
} else {
$sysname = strtolower($sysname);
}
if (isset($cpumap[$cpu])) {
$cpu = $cpumap[$cpu];
}
return array($sysname, $release, $cpu, $extra, $nodename);
}
function _detectGlibcVersion()
{
static $glibc = false;
if ($glibc !== false) {
return $glibc; // no need to run this multiple times
}
$major = $minor = 0;
include_once "System.php";
// Use glibc's <features.h> header file to
// get major and minor version number:
if (@file_exists('/usr/include/features.h') &&
@is_readable('/usr/include/features.h')) {
if (!@file_exists('/usr/bin/cpp') || !@is_executable('/usr/bin/cpp')) {
$features_file = fopen('/usr/include/features.h', 'rb');
while (!feof($features_file)) {
$line = fgets($features_file, 8192);
if (!$line || (strpos($line, '#define') === false)) {
continue;
}
if (strpos($line, '__GLIBC__')) {
// major version number #define __GLIBC__ version
$line = preg_split('/\s+/', $line);
$glibc_major = trim($line[2]);
if (isset($glibc_minor)) {
break;
}
continue;
}
if (strpos($line, '__GLIBC_MINOR__')) {
// got the minor version number
// #define __GLIBC_MINOR__ version
$line = preg_split('/\s+/', $line);
$glibc_minor = trim($line[2]);
if (isset($glibc_major)) {
break;
}
continue;
}
}
fclose($features_file);
if (!isset($glibc_major) || !isset($glibc_minor)) {
return $glibc = '';
}
return $glibc = 'glibc' . trim($glibc_major) . "." . trim($glibc_minor) ;
} // no cpp
$tmpfile = System::mktemp("glibctest");
$fp = fopen($tmpfile, "w");
fwrite($fp, "#include <features.h>\n__GLIBC__ __GLIBC_MINOR__\n");
fclose($fp);
$cpp = popen("/usr/bin/cpp $tmpfile", "r");
while ($line = fgets($cpp, 1024)) {
if ($line{0} == '#' || trim($line) == '') {
continue;
}
if (list($major, $minor) = explode(' ', trim($line))) {
break;
}
}
pclose($cpp);
unlink($tmpfile);
} // features.h
if (!($major && $minor) && @is_link('/lib/libc.so.6')) {
// Let's try reading the libc.so.6 symlink
if (preg_match('/^libc-(.*)\.so$/', basename(readlink('/lib/libc.so.6')), $matches)) {
list($major, $minor) = explode('.', $matches[1]);
}
}
if (!($major && $minor)) {
return $glibc = '';
}
return $glibc = "glibc{$major}.{$minor}";
}
function getSignature()
{
if (empty($this->extra)) {
return "{$this->sysname}-{$this->release}-{$this->cpu}";
}
return "{$this->sysname}-{$this->release}-{$this->cpu}-{$this->extra}";
}
function getSysname()
{
return $this->sysname;
}
function getNodename()
{
return $this->nodename;
}
function getCpu()
{
return $this->cpu;
}
function getRelease()
{
return $this->release;
}
function getExtra()
{
return $this->extra;
}
function matchSignature($match)
{
$fragments = is_array($match) ? $match : explode('-', $match);
$n = count($fragments);
$matches = 0;
if ($n > 0) {
$matches += $this->_matchFragment($fragments[0], $this->sysname);
}
if ($n > 1) {
$matches += $this->_matchFragment($fragments[1], $this->release);
}
if ($n > 2) {
$matches += $this->_matchFragment($fragments[2], $this->cpu);
}
if ($n > 3) {
$matches += $this->_matchFragment($fragments[3], $this->extra);
}
return ($matches == $n);
}
function _matchFragment($fragment, $value)
{
if (strcspn($fragment, '*?') < strlen($fragment)) {
$reg = '/^' . str_replace(array('*', '?', '/'), array('.*', '.', '\\/'), $fragment) . '\\z/';
return preg_match($reg, $value);
}
return ($fragment == '*' || !strcasecmp($fragment, $value));
}
}
/*
* Local Variables:
* indent-tabs-mode: nil
* c-basic-offset: 4
* End:
*/
<?php
/**
* Dummy file to make autoloaders work
*
* PHP version 5
*
* @category PEAR
* @package PEAR
* @author Christian Weiske <cweiske@php.net>
* @license http://opensource.org/licenses/bsd-license.php New BSD License
* @link http://pear.php.net/package/PEAR
*/
require_once __DIR__ . '/../PEAR.php';
?><?php
/**
* Error Stack Implementation
*
* This is an incredibly simple implementation of a very complex error handling
* facility. It contains the ability
* to track multiple errors from multiple packages simultaneously. In addition,
* it can track errors of many levels, save data along with the error, context
* information such as the exact file, line number, class and function that
* generated the error, and if necessary, it can raise a traditional PEAR_Error.
* It has built-in support for PEAR::Log, to log errors as they occur
*
* Since version 0.2alpha, it is also possible to selectively ignore errors,
* through the use of an error callback, see {@link pushCallback()}
*
* Since version 0.3alpha, it is possible to specify the exception class
* returned from {@link push()}
*
* Since version PEAR1.3.2, ErrorStack no longer instantiates an exception class. This can
* still be done quite handily in an error callback or by manipulating the returned array
* @category Debugging
* @package PEAR_ErrorStack
* @author Greg Beaver <cellog@php.net>
* @copyright 2004-2008 Greg Beaver
* @license http://opensource.org/licenses/bsd-license.php New BSD License
* @link http://pear.php.net/package/PEAR_ErrorStack
*/
/**
* Singleton storage
*
* Format:
* <pre>
* array(
* 'package1' => PEAR_ErrorStack object,
* 'package2' => PEAR_ErrorStack object,
* ...
* )
* </pre>
* @access private
* @global array $GLOBALS['_PEAR_ERRORSTACK_SINGLETON']
*/
$GLOBALS['_PEAR_ERRORSTACK_SINGLETON'] = array();
/**
* Global error callback (default)
*
* This is only used if set to non-false. * is the default callback for
* all packages, whereas specific packages may set a default callback
* for all instances, regardless of whether they are a singleton or not.
*
* To exclude non-singletons, only set the local callback for the singleton
* @see PEAR_ErrorStack::setDefaultCallback()
* @access private
* @global array $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_CALLBACK']
*/
$GLOBALS['_PEAR_ERRORSTACK_DEFAULT_CALLBACK'] = array(
'*' => false,
);
/**
* Global Log object (default)
*
* This is only used if set to non-false. Use to set a default log object for
* all stacks, regardless of instantiation order or location
* @see PEAR_ErrorStack::setDefaultLogger()
* @access private
* @global array $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER']
*/
$GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER'] = false;
/**
* Global Overriding Callback
*
* This callback will override any error callbacks that specific loggers have set.
* Use with EXTREME caution
* @see PEAR_ErrorStack::staticPushCallback()
* @access private
* @global array $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER']
*/
$GLOBALS['_PEAR_ERRORSTACK_OVERRIDE_CALLBACK'] = array();
/**#@+
* One of four possible return values from the error Callback
* @see PEAR_ErrorStack::_errorCallback()
*/
/**
* If this is returned, then the error will be both pushed onto the stack
* and logged.
*/
define('PEAR_ERRORSTACK_PUSHANDLOG', 1);
/**
* If this is returned, then the error will only be pushed onto the stack,
* and not logged.
*/
define('PEAR_ERRORSTACK_PUSH', 2);
/**
* If this is returned, then the error will only be logged, but not pushed
* onto the error stack.
*/
define('PEAR_ERRORSTACK_LOG', 3);
/**
* If this is returned, then the error is completely ignored.
*/
define('PEAR_ERRORSTACK_IGNORE', 4);
/**
* If this is returned, then the error is logged and die() is called.
*/
define('PEAR_ERRORSTACK_DIE', 5);
/**#@-*/
/**
* Error code for an attempt to instantiate a non-class as a PEAR_ErrorStack in
* the singleton method.
*/
define('PEAR_ERRORSTACK_ERR_NONCLASS', 1);
/**
* Error code for an attempt to pass an object into {@link PEAR_ErrorStack::getMessage()}
* that has no __toString() method
*/
define('PEAR_ERRORSTACK_ERR_OBJTOSTRING', 2);
/**
* Error Stack Implementation
*
* Usage:
* <code>
* // global error stack
* $global_stack = &PEAR_ErrorStack::singleton('MyPackage');
* // local error stack
* $local_stack = new PEAR_ErrorStack('MyPackage');
* </code>
* @author Greg Beaver <cellog@php.net>
* @version @package_version@
* @package PEAR_ErrorStack
* @category Debugging
* @copyright 2004-2008 Greg Beaver
* @license http://opensource.org/licenses/bsd-license.php New BSD License
* @link http://pear.php.net/package/PEAR_ErrorStack
*/
class PEAR_ErrorStack {
/**
* Errors are stored in the order that they are pushed on the stack.
* @since 0.4alpha Errors are no longer organized by error level.
* This renders pop() nearly unusable, and levels could be more easily
* handled in a callback anyway
* @var array
* @access private
*/
var $_errors = array();
/**
* Storage of errors by level.
*
* Allows easy retrieval and deletion of only errors from a particular level
* @since PEAR 1.4.0dev
* @var array
* @access private
*/
var $_errorsByLevel = array();
/**
* Package name this error stack represents
* @var string
* @access protected
*/
var $_package;
/**
* Determines whether a PEAR_Error is thrown upon every error addition
* @var boolean
* @access private
*/
var $_compat = false;
/**
* If set to a valid callback, this will be used to generate the error
* message from the error code, otherwise the message passed in will be
* used
* @var false|string|array
* @access private
*/
var $_msgCallback = false;
/**
* If set to a valid callback, this will be used to generate the error
* context for an error. For PHP-related errors, this will be a file
* and line number as retrieved from debug_backtrace(), but can be
* customized for other purposes. The error might actually be in a separate
* configuration file, or in a database query.
* @var false|string|array
* @access protected
*/
var $_contextCallback = false;
/**
* If set to a valid callback, this will be called every time an error
* is pushed onto the stack. The return value will be used to determine
* whether to allow an error to be pushed or logged.
*
* The return value must be one an PEAR_ERRORSTACK_* constant
* @see PEAR_ERRORSTACK_PUSHANDLOG, PEAR_ERRORSTACK_PUSH, PEAR_ERRORSTACK_LOG
* @var false|string|array
* @access protected
*/
var $_errorCallback = array();
/**
* PEAR::Log object for logging errors
* @var false|Log
* @access protected
*/
var $_logger = false;
/**
* Error messages - designed to be overridden
* @var array
* @abstract
*/
var $_errorMsgs = array();
/**
* Set up a new error stack
*
* @param string $package name of the package this error stack represents
* @param callback $msgCallback callback used for error message generation
* @param callback $contextCallback callback used for context generation,
* defaults to {@link getFileLine()}
* @param boolean $throwPEAR_Error
*/
function __construct($package, $msgCallback = false, $contextCallback = false,
$throwPEAR_Error = false)
{
$this->_package = $package;
$this->setMessageCallback($msgCallback);
$this->setContextCallback($contextCallback);
$this->_compat = $throwPEAR_Error;
}
/**
* Return a single error stack for this package.
*
* Note that all parameters are ignored if the stack for package $package
* has already been instantiated
* @param string $package name of the package this error stack represents
* @param callback $msgCallback callback used for error message generation
* @param callback $contextCallback callback used for context generation,
* defaults to {@link getFileLine()}
* @param boolean $throwPEAR_Error
* @param string $stackClass class to instantiate
*
* @return PEAR_ErrorStack
*/
public static function &singleton(
$package, $msgCallback = false, $contextCallback = false,
$throwPEAR_Error = false, $stackClass = 'PEAR_ErrorStack'
) {
if (isset($GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package])) {
return $GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package];
}
if (!class_exists($stackClass)) {
if (function_exists('debug_backtrace')) {
$trace = debug_backtrace();
}
PEAR_ErrorStack::staticPush('PEAR_ErrorStack', PEAR_ERRORSTACK_ERR_NONCLASS,
'exception', array('stackclass' => $stackClass),
'stack class "%stackclass%" is not a valid class name (should be like PEAR_ErrorStack)',
false, $trace);
}
$GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package] =
new $stackClass($package, $msgCallback, $contextCallback, $throwPEAR_Error);
return $GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package];
}
/**
* Internal error handler for PEAR_ErrorStack class
*
* Dies if the error is an exception (and would have died anyway)
* @access private
*/
function _handleError($err)
{
if ($err['level'] == 'exception') {
$message = $err['message'];
if (isset($_SERVER['REQUEST_URI'])) {
echo '<br />';
} else {
echo "\n";
}
var_dump($err['context']);
die($message);
}
}
/**
* Set up a PEAR::Log object for all error stacks that don't have one
* @param Log $log
*/
public static function setDefaultLogger(&$log)
{
if (is_object($log) && method_exists($log, 'log') ) {
$GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER'] = &$log;
} elseif (is_callable($log)) {
$GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER'] = &$log;
}
}
/**
* Set up a PEAR::Log object for this error stack
* @param Log $log
*/
function setLogger(&$log)
{
if (is_object($log) && method_exists($log, 'log') ) {
$this->_logger = &$log;
} elseif (is_callable($log)) {
$this->_logger = &$log;
}
}
/**
* Set an error code => error message mapping callback
*
* This method sets the callback that can be used to generate error
* messages for any instance
* @param array|string Callback function/method
*/
function setMessageCallback($msgCallback)
{
if (!$msgCallback) {
$this->_msgCallback = array(&$this, 'getErrorMessage');
} else {
if (is_callable($msgCallback)) {
$this->_msgCallback = $msgCallback;
}
}
}
/**
* Get an error code => error message mapping callback
*
* This method returns the current callback that can be used to generate error
* messages
* @return array|string|false Callback function/method or false if none
*/
function getMessageCallback()
{
return $this->_msgCallback;
}
/**
* Sets a default callback to be used by all error stacks
*
* This method sets the callback that can be used to generate error
* messages for a singleton
* @param array|string Callback function/method
* @param string Package name, or false for all packages
*/
public static function setDefaultCallback($callback = false, $package = false)
{
if (!is_callable($callback)) {
$callback = false;
}
$package = $package ? $package : '*';
$GLOBALS['_PEAR_ERRORSTACK_DEFAULT_CALLBACK'][$package] = $callback;
}
/**
* Set a callback that generates context information (location of error) for an error stack
*
* This method sets the callback that can be used to generate context
* information for an error. Passing in NULL will disable context generation
* and remove the expensive call to debug_backtrace()
* @param array|string|null Callback function/method
*/
function setContextCallback($contextCallback)
{
if ($contextCallback === null) {
return $this->_contextCallback = false;
}
if (!$contextCallback) {
$this->_contextCallback = array(&$this, 'getFileLine');
} else {
if (is_callable($contextCallback)) {
$this->_contextCallback = $contextCallback;
}
}
}
/**
* Set an error Callback
* If set to a valid callback, this will be called every time an error
* is pushed onto the stack. The return value will be used to determine
* whether to allow an error to be pushed or logged.
*
* The return value must be one of the ERRORSTACK_* constants.
*
* This functionality can be used to emulate PEAR's pushErrorHandling, and
* the PEAR_ERROR_CALLBACK mode, without affecting the integrity of
* the error stack or logging
* @see PEAR_ERRORSTACK_PUSHANDLOG, PEAR_ERRORSTACK_PUSH, PEAR_ERRORSTACK_LOG
* @see popCallback()
* @param string|array $cb
*/
function pushCallback($cb)
{
array_push($this->_errorCallback, $cb);
}
/**
* Remove a callback from the error callback stack
* @see pushCallback()
* @return array|string|false
*/
function popCallback()
{
if (!count($this->_errorCallback)) {
return false;
}
return array_pop($this->_errorCallback);
}
/**
* Set a temporary overriding error callback for every package error stack
*
* Use this to temporarily disable all existing callbacks (can be used
* to emulate the @ operator, for instance)
* @see PEAR_ERRORSTACK_PUSHANDLOG, PEAR_ERRORSTACK_PUSH, PEAR_ERRORSTACK_LOG
* @see staticPopCallback(), pushCallback()
* @param string|array $cb
*/
public static function staticPushCallback($cb)
{
array_push($GLOBALS['_PEAR_ERRORSTACK_OVERRIDE_CALLBACK'], $cb);
}
/**
* Remove a temporary overriding error callback
* @see staticPushCallback()
* @return array|string|false
*/
public static function staticPopCallback()
{
$ret = array_pop($GLOBALS['_PEAR_ERRORSTACK_OVERRIDE_CALLBACK']);
if (!is_array($GLOBALS['_PEAR_ERRORSTACK_OVERRIDE_CALLBACK'])) {
$GLOBALS['_PEAR_ERRORSTACK_OVERRIDE_CALLBACK'] = array();
}
return $ret;
}
/**
* Add an error to the stack
*
* If the message generator exists, it is called with 2 parameters.
* - the current Error Stack object
* - an array that is in the same format as an error. Available indices
* are 'code', 'package', 'time', 'params', 'level', and 'context'
*
* Next, if the error should contain context information, this is
* handled by the context grabbing method.
* Finally, the error is pushed onto the proper error stack
* @param int $code Package-specific error code
* @param string $level Error level. This is NOT spell-checked
* @param array $params associative array of error parameters
* @param string $msg Error message, or a portion of it if the message
* is to be generated
* @param array $repackage If this error re-packages an error pushed by
* another package, place the array returned from
* {@link pop()} in this parameter
* @param array $backtrace Protected parameter: use this to pass in the
* {@link debug_backtrace()} that should be used
* to find error context
* @return PEAR_Error|array if compatibility mode is on, a PEAR_Error is also
* thrown. If a PEAR_Error is returned, the userinfo
* property is set to the following array:
*
* <code>
* array(
* 'code' => $code,
* 'params' => $params,
* 'package' => $this->_package,
* 'level' => $level,
* 'time' => time(),
* 'context' => $context,
* 'message' => $msg,
* //['repackage' => $err] repackaged error array/Exception class
* );
* </code>
*
* Normally, the previous array is returned.
*/
function push($code, $level = 'error', $params = array(), $msg = false,
$repackage = false, $backtrace = false)
{
$context = false;
// grab error context
if ($this->_contextCallback) {
if (!$backtrace) {
$backtrace = debug_backtrace();
}
$context = call_user_func($this->_contextCallback, $code, $params, $backtrace);
}
// save error
$time = explode(' ', microtime());
$time = $time[1] + $time[0];
$err = array(
'code' => $code,
'params' => $params,
'package' => $this->_package,
'level' => $level,
'time' => $time,
'context' => $context,
'message' => $msg,
);
if ($repackage) {
$err['repackage'] = $repackage;
}
// set up the error message, if necessary
if ($this->_msgCallback) {
$msg = call_user_func_array($this->_msgCallback,
array(&$this, $err));
$err['message'] = $msg;
}
$push = $log = true;
$die = false;
// try the overriding callback first
$callback = $this->staticPopCallback();
if ($callback) {
$this->staticPushCallback($callback);
}
if (!is_callable($callback)) {
// try the local callback next
$callback = $this->popCallback();
if (is_callable($callback)) {
$this->pushCallback($callback);
} else {
// try the default callback
$callback = isset($GLOBALS['_PEAR_ERRORSTACK_DEFAULT_CALLBACK'][$this->_package]) ?
$GLOBALS['_PEAR_ERRORSTACK_DEFAULT_CALLBACK'][$this->_package] :
$GLOBALS['_PEAR_ERRORSTACK_DEFAULT_CALLBACK']['*'];
}
}
if (is_callable($callback)) {
switch(call_user_func($callback, $err)){
case PEAR_ERRORSTACK_IGNORE:
return $err;
break;
case PEAR_ERRORSTACK_PUSH:
$log = false;
break;
case PEAR_ERRORSTACK_LOG:
$push = false;
break;
case PEAR_ERRORSTACK_DIE:
$die = true;
break;
// anything else returned has the same effect as pushandlog
}
}
if ($push) {
array_unshift($this->_errors, $err);
if (!isset($this->_errorsByLevel[$err['level']])) {
$this->_errorsByLevel[$err['level']] = array();
}
$this->_errorsByLevel[$err['level']][] = &$this->_errors[0];
}
if ($log) {
if ($this->_logger || $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER']) {
$this->_log($err);
}
}
if ($die) {
die();
}
if ($this->_compat && $push) {
return $this->raiseError($msg, $code, null, null, $err);
}
return $err;
}
/**
* Static version of {@link push()}
*
* @param string $package Package name this error belongs to
* @param int $code Package-specific error code
* @param string $level Error level. This is NOT spell-checked
* @param array $params associative array of error parameters
* @param string $msg Error message, or a portion of it if the message
* is to be generated
* @param array $repackage If this error re-packages an error pushed by
* another package, place the array returned from
* {@link pop()} in this parameter
* @param array $backtrace Protected parameter: use this to pass in the
* {@link debug_backtrace()} that should be used
* to find error context
* @return PEAR_Error|array if compatibility mode is on, a PEAR_Error is also
* thrown. see docs for {@link push()}
*/
public static function staticPush(
$package, $code, $level = 'error', $params = array(),
$msg = false, $repackage = false, $backtrace = false
) {
$s = &PEAR_ErrorStack::singleton($package);
if ($s->_contextCallback) {
if (!$backtrace) {
if (function_exists('debug_backtrace')) {
$backtrace = debug_backtrace();
}
}
}
return $s->push($code, $level, $params, $msg, $repackage, $backtrace);
}
/**
* Log an error using PEAR::Log
* @param array $err Error array
* @param array $levels Error level => Log constant map
* @access protected
*/
function _log($err)
{
if ($this->_logger) {
$logger = &$this->_logger;
} else {
$logger = &$GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER'];
}
if (is_a($logger, 'Log')) {
$levels = array(
'exception' => PEAR_LOG_CRIT,
'alert' => PEAR_LOG_ALERT,
'critical' => PEAR_LOG_CRIT,
'error' => PEAR_LOG_ERR,
'warning' => PEAR_LOG_WARNING,
'notice' => PEAR_LOG_NOTICE,
'info' => PEAR_LOG_INFO,
'debug' => PEAR_LOG_DEBUG);
if (isset($levels[$err['level']])) {
$level = $levels[$err['level']];
} else {
$level = PEAR_LOG_INFO;
}
$logger->log($err['message'], $level, $err);
} else { // support non-standard logs
call_user_func($logger, $err);
}
}
/**
* Pop an error off of the error stack
*
* @return false|array
* @since 0.4alpha it is no longer possible to specify a specific error
* level to return - the last error pushed will be returned, instead
*/
function pop()
{
$err = @array_shift($this->_errors);
if (!is_null($err)) {
@array_pop($this->_errorsByLevel[$err['level']]);
if (!count($this->_errorsByLevel[$err['level']])) {
unset($this->_errorsByLevel[$err['level']]);
}
}
return $err;
}
/**
* Pop an error off of the error stack, static method
*
* @param string package name
* @return boolean
* @since PEAR1.5.0a1
*/
function staticPop($package)
{
if ($package) {
if (!isset($GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package])) {
return false;
}
return $GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package]->pop();
}
}
/**
* Determine whether there are any errors on the stack
* @param string|array Level name. Use to determine if any errors
* of level (string), or levels (array) have been pushed
* @return boolean
*/
function hasErrors($level = false)
{
if ($level) {
return isset($this->_errorsByLevel[$level]);
}
return count($this->_errors);
}
/**
* Retrieve all errors since last purge
*
* @param boolean set in order to empty the error stack
* @param string level name, to return only errors of a particular severity
* @return array
*/
function getErrors($purge = false, $level = false)
{
if (!$purge) {
if ($level) {
if (!isset($this->_errorsByLevel[$level])) {
return array();
} else {
return $this->_errorsByLevel[$level];
}
} else {
return $this->_errors;
}
}
if ($level) {
$ret = $this->_errorsByLevel[$level];
foreach ($this->_errorsByLevel[$level] as $i => $unused) {
// entries are references to the $_errors array
$this->_errorsByLevel[$level][$i] = false;
}
// array_filter removes all entries === false
$this->_errors = array_filter($this->_errors);
unset($this->_errorsByLevel[$level]);
return $ret;
}
$ret = $this->_errors;
$this->_errors = array();
$this->_errorsByLevel = array();
return $ret;
}
/**
* Determine whether there are any errors on a single error stack, or on any error stack
*
* The optional parameter can be used to test the existence of any errors without the need of
* singleton instantiation
* @param string|false Package name to check for errors
* @param string Level name to check for a particular severity
* @return boolean
*/
public static function staticHasErrors($package = false, $level = false)
{
if ($package) {
if (!isset($GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package])) {
return false;
}
return $GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package]->hasErrors($level);
}
foreach ($GLOBALS['_PEAR_ERRORSTACK_SINGLETON'] as $package => $obj) {
if ($obj->hasErrors($level)) {
return true;
}
}
return false;
}
/**
* Get a list of all errors since last purge, organized by package
* @since PEAR 1.4.0dev BC break! $level is now in the place $merge used to be
* @param boolean $purge Set to purge the error stack of existing errors
* @param string $level Set to a level name in order to retrieve only errors of a particular level
* @param boolean $merge Set to return a flat array, not organized by package
* @param array $sortfunc Function used to sort a merged array - default
* sorts by time, and should be good for most cases
*
* @return array
*/
public static function staticGetErrors(
$purge = false, $level = false, $merge = false,
$sortfunc = array('PEAR_ErrorStack', '_sortErrors')
) {
$ret = array();
if (!is_callable($sortfunc)) {
$sortfunc = array('PEAR_ErrorStack', '_sortErrors');
}
foreach ($GLOBALS['_PEAR_ERRORSTACK_SINGLETON'] as $package => $obj) {
$test = $GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package]->getErrors($purge, $level);
if ($test) {
if ($merge) {
$ret = array_merge($ret, $test);
} else {
$ret[$package] = $test;
}
}
}
if ($merge) {
usort($ret, $sortfunc);
}
return $ret;
}
/**
* Error sorting function, sorts by time
* @access private
*/
public static function _sortErrors($a, $b)
{
if ($a['time'] == $b['time']) {
return 0;
}
if ($a['time'] < $b['time']) {
return 1;
}
return -1;
}
/**
* Standard file/line number/function/class context callback
*
* This function uses a backtrace generated from {@link debug_backtrace()}
* and so will not work at all in PHP < 4.3.0. The frame should
* reference the frame that contains the source of the error.
* @return array|false either array('file' => file, 'line' => line,
* 'function' => function name, 'class' => class name) or
* if this doesn't work, then false
* @param unused
* @param integer backtrace frame.
* @param array Results of debug_backtrace()
*/
public static function getFileLine($code, $params, $backtrace = null)
{
if ($backtrace === null) {
return false;
}
$frame = 0;
$functionframe = 1;
if (!isset($backtrace[1])) {
$functionframe = 0;
} else {
while (isset($backtrace[$functionframe]['function']) &&
$backtrace[$functionframe]['function'] == 'eval' &&
isset($backtrace[$functionframe + 1])) {
$functionframe++;
}
}
if (isset($backtrace[$frame])) {
if (!isset($backtrace[$frame]['file'])) {
$frame++;
}
$funcbacktrace = $backtrace[$functionframe];
$filebacktrace = $backtrace[$frame];
$ret = array('file' => $filebacktrace['file'],
'line' => $filebacktrace['line']);
// rearrange for eval'd code or create function errors
if (strpos($filebacktrace['file'], '(') &&
preg_match(';^(.*?)\((\d+)\) : (.*?)\\z;', $filebacktrace['file'],
$matches)) {
$ret['file'] = $matches[1];
$ret['line'] = $matches[2] + 0;
}
if (isset($funcbacktrace['function']) && isset($backtrace[1])) {
if ($funcbacktrace['function'] != 'eval') {
if ($funcbacktrace['function'] == '__lambda_func') {
$ret['function'] = 'create_function() code';
} else {
$ret['function'] = $funcbacktrace['function'];
}
}
}
if (isset($funcbacktrace['class']) && isset($backtrace[1])) {
$ret['class'] = $funcbacktrace['class'];
}
return $ret;
}
return false;
}
/**
* Standard error message generation callback
*
* This method may also be called by a custom error message generator
* to fill in template values from the params array, simply
* set the third parameter to the error message template string to use
*
* The special variable %__msg% is reserved: use it only to specify
* where a message passed in by the user should be placed in the template,
* like so:
*
* Error message: %msg% - internal error
*
* If the message passed like so:
*
* <code>
* $stack->push(ERROR_CODE, 'error', array(), 'server error 500');
* </code>
*
* The returned error message will be "Error message: server error 500 -
* internal error"
* @param PEAR_ErrorStack
* @param array
* @param string|false Pre-generated error message template
*
* @return string
*/
public static function getErrorMessage(&$stack, $err, $template = false)
{
if ($template) {
$mainmsg = $template;
} else {
$mainmsg = $stack->getErrorMessageTemplate($err['code']);
}
$mainmsg = str_replace('%__msg%', $err['message'], $mainmsg);
if (is_array($err['params']) && count($err['params'])) {
foreach ($err['params'] as $name => $val) {
if (is_array($val)) {
// @ is needed in case $val is a multi-dimensional array
$val = @implode(', ', $val);
}
if (is_object($val)) {
if (method_exists($val, '__toString')) {
$val = $val->__toString();
} else {
PEAR_ErrorStack::staticPush('PEAR_ErrorStack', PEAR_ERRORSTACK_ERR_OBJTOSTRING,
'warning', array('obj' => get_class($val)),
'object %obj% passed into getErrorMessage, but has no __toString() method');
$val = 'Object';
}
}
$mainmsg = str_replace('%' . $name . '%', $val, $mainmsg);
}
}
return $mainmsg;
}
/**
* Standard Error Message Template generator from code
* @return string
*/
function getErrorMessageTemplate($code)
{
if (!isset($this->_errorMsgs[$code])) {
return '%__msg%';
}
return $this->_errorMsgs[$code];
}
/**
* Set the Error Message Template array
*
* The array format must be:
* <pre>
* array(error code => 'message template',...)
* </pre>
*
* Error message parameters passed into {@link push()} will be used as input
* for the error message. If the template is 'message %foo% was %bar%', and the
* parameters are array('foo' => 'one', 'bar' => 'six'), the error message returned will
* be 'message one was six'
* @return string
*/
function setErrorMessageTemplate($template)
{
$this->_errorMsgs = $template;
}
/**
* emulate PEAR::raiseError()
*
* @return PEAR_Error
*/
function raiseError()
{
require_once 'PEAR.php';
$args = func_get_args();
return call_user_func_array(array('PEAR', 'raiseError'), $args);
}
}
$stack = &PEAR_ErrorStack::singleton('PEAR_ErrorStack');
$stack->pushCallback(array('PEAR_ErrorStack', '_handleError'));
?>
<?php
/**
* PEAR, the PHP Extension and Application Repository
*
* PEAR class and PEAR_Error class
*
* PHP versions 4 and 5
*
* @category pear
* @package PEAR
* @author Sterling Hughes <sterling@php.net>
* @author Stig Bakken <ssb@php.net>
* @author Tomas V.V.Cox <cox@idecnet.com>
* @author Greg Beaver <cellog@php.net>
* @copyright 1997-2010 The Authors
* @license http://opensource.org/licenses/bsd-license.php New BSD License
* @link http://pear.php.net/package/PEAR
* @since File available since Release 0.1
*/
/**#@+
* ERROR constants
*/
define('PEAR_ERROR_RETURN', 1);
define('PEAR_ERROR_PRINT', 2);
define('PEAR_ERROR_TRIGGER', 4);
define('PEAR_ERROR_DIE', 8);
define('PEAR_ERROR_CALLBACK', 16);
/**
* WARNING: obsolete
* @deprecated
*/
define('PEAR_ERROR_EXCEPTION', 32);
/**#@-*/
if (substr(PHP_OS, 0, 3) == 'WIN') {
define('OS_WINDOWS', true);
define('OS_UNIX', false);
define('PEAR_OS', 'Windows');
} else {
define('OS_WINDOWS', false);
define('OS_UNIX', true);
define('PEAR_OS', 'Unix'); // blatant assumption
}
$GLOBALS['_PEAR_default_error_mode'] = PEAR_ERROR_RETURN;
$GLOBALS['_PEAR_default_error_options'] = E_USER_NOTICE;
$GLOBALS['_PEAR_destructor_object_list'] = array();
$GLOBALS['_PEAR_shutdown_funcs'] = array();
$GLOBALS['_PEAR_error_handler_stack'] = array();
@ini_set('track_errors', true);
/**
* Base class for other PEAR classes. Provides rudimentary
* emulation of destructors.
*
* If you want a destructor in your class, inherit PEAR and make a
* destructor method called _yourclassname (same name as the
* constructor, but with a "_" prefix). Also, in your constructor you
* have to call the PEAR constructor: $this->PEAR();.
* The destructor method will be called without parameters. Note that
* at in some SAPI implementations (such as Apache), any output during
* the request shutdown (in which destructors are called) seems to be
* discarded. If you need to get any debug information from your
* destructor, use error_log(), syslog() or something similar.
*
* IMPORTANT! To use the emulated destructors you need to create the
* objects by reference: $obj =& new PEAR_child;
*
* @category pear
* @package PEAR
* @author Stig Bakken <ssb@php.net>
* @author Tomas V.V. Cox <cox@idecnet.com>
* @author Greg Beaver <cellog@php.net>
* @copyright 1997-2006 The PHP Group
* @license http://opensource.org/licenses/bsd-license.php New BSD License
* @version Release: @package_version@
* @link http://pear.php.net/package/PEAR
* @see PEAR_Error
* @since Class available since PHP 4.0.2
* @link http://pear.php.net/manual/en/core.pear.php#core.pear.pear
*/
class PEAR
{
/**
* Whether to enable internal debug messages.
*
* @var bool
* @access private
*/
var $_debug = false;
/**
* Default error mode for this object.
*
* @var int
* @access private
*/
var $_default_error_mode = null;
/**
* Default error options used for this object when error mode
* is PEAR_ERROR_TRIGGER.
*
* @var int
* @access private
*/
var $_default_error_options = null;
/**
* Default error handler (callback) for this object, if error mode is
* PEAR_ERROR_CALLBACK.
*
* @var string
* @access private
*/
var $_default_error_handler = '';
/**
* Which class to use for error objects.
*
* @var string
* @access private
*/
var $_error_class = 'PEAR_Error';
/**
* An array of expected errors.
*
* @var array
* @access private
*/
var $_expected_errors = array();
/**
* List of methods that can be called both statically and non-statically.
* @var array
*/
protected static $bivalentMethods = array(
'setErrorHandling' => true,
'raiseError' => true,
'throwError' => true,
'pushErrorHandling' => true,
'popErrorHandling' => true,
);
/**
* Constructor. Registers this object in
* $_PEAR_destructor_object_list for destructor emulation if a
* destructor object exists.
*
* @param string $error_class (optional) which class to use for
* error objects, defaults to PEAR_Error.
* @access public
* @return void
*/
function __construct($error_class = null)
{
$classname = strtolower(get_class($this));
if ($this->_debug) {
print "PEAR constructor called, class=$classname\n";
}
if ($error_class !== null) {
$this->_error_class = $error_class;
}
while ($classname && strcasecmp($classname, "pear")) {
$destructor = "_$classname";
if (method_exists($this, $destructor)) {
global $_PEAR_destructor_object_list;
$_PEAR_destructor_object_list[] = &$this;
if (!isset($GLOBALS['_PEAR_SHUTDOWN_REGISTERED'])) {
register_shutdown_function("_PEAR_call_destructors");
$GLOBALS['_PEAR_SHUTDOWN_REGISTERED'] = true;
}
break;
} else {
$classname = get_parent_class($classname);
}
}
}
/**
* Only here for backwards compatibility.
* E.g. Archive_Tar calls $this->PEAR() in its constructor.
*
* @param string $error_class Which class to use for error objects,
* defaults to PEAR_Error.
*/
public function PEAR($error_class = null)
{
self::__construct($error_class);
}
/**
* Destructor (the emulated type of...). Does nothing right now,
* but is included for forward compatibility, so subclass
* destructors should always call it.
*
* See the note in the class desciption about output from
* destructors.
*
* @access public
* @return void
*/
function _PEAR() {
if ($this->_debug) {
printf("PEAR destructor called, class=%s\n", strtolower(get_class($this)));
}
}
public function __call($method, $arguments)
{
if (!isset(self::$bivalentMethods[$method])) {
trigger_error(
'Call to undefined method PEAR::' . $method . '()', E_USER_ERROR
);
}
return call_user_func_array(
array(get_class(), '_' . $method),
array_merge(array($this), $arguments)
);
}
public static function __callStatic($method, $arguments)
{
if (!isset(self::$bivalentMethods[$method])) {
trigger_error(
'Call to undefined method PEAR::' . $method . '()', E_USER_ERROR
);
}
return call_user_func_array(
array(get_class(), '_' . $method),
array_merge(array(null), $arguments)
);
}
/**
* If you have a class that's mostly/entirely static, and you need static
* properties, you can use this method to simulate them. Eg. in your method(s)
* do this: $myVar = &PEAR::getStaticProperty('myclass', 'myVar');
* You MUST use a reference, or they will not persist!
*
* @param string $class The calling classname, to prevent clashes
* @param string $var The variable to retrieve.
* @return mixed A reference to the variable. If not set it will be
* auto initialised to NULL.
*/
public static function &getStaticProperty($class, $var)
{
static $properties;
if (!isset($properties[$class])) {
$properties[$class] = array();
}
if (!array_key_exists($var, $properties[$class])) {
$properties[$class][$var] = null;
}
return $properties[$class][$var];
}
/**
* Use this function to register a shutdown method for static
* classes.
*
* @param mixed $func The function name (or array of class/method) to call
* @param mixed $args The arguments to pass to the function
*
* @return void
*/
public static function registerShutdownFunc($func, $args = array())
{
// if we are called statically, there is a potential
// that no shutdown func is registered. Bug #6445
if (!isset($GLOBALS['_PEAR_SHUTDOWN_REGISTERED'])) {
register_shutdown_function("_PEAR_call_destructors");
$GLOBALS['_PEAR_SHUTDOWN_REGISTERED'] = true;
}
$GLOBALS['_PEAR_shutdown_funcs'][] = array($func, $args);
}
/**
* Tell whether a value is a PEAR error.
*
* @param mixed $data the value to test
* @param int $code if $data is an error object, return true
* only if $code is a string and
* $obj->getMessage() == $code or
* $code is an integer and $obj->getCode() == $code
*
* @return bool true if parameter is an error
*/
public static function isError($data, $code = null)
{
if (!is_a($data, 'PEAR_Error')) {
return false;
}
if (is_null($code)) {
return true;
} elseif (is_string($code)) {
return $data->getMessage() == $code;
}
return $data->getCode() == $code;
}
/**
* Sets how errors generated by this object should be handled.
* Can be invoked both in objects and statically. If called
* statically, setErrorHandling sets the default behaviour for all
* PEAR objects. If called in an object, setErrorHandling sets
* the default behaviour for that object.
*
* @param object $object
* Object the method was called on (non-static mode)
*
* @param int $mode
* One of PEAR_ERROR_RETURN, PEAR_ERROR_PRINT,
* PEAR_ERROR_TRIGGER, PEAR_ERROR_DIE,
* PEAR_ERROR_CALLBACK or PEAR_ERROR_EXCEPTION.
*
* @param mixed $options
* When $mode is PEAR_ERROR_TRIGGER, this is the error level (one
* of E_USER_NOTICE, E_USER_WARNING or E_USER_ERROR).
*
* When $mode is PEAR_ERROR_CALLBACK, this parameter is expected
* to be the callback function or method. A callback
* function is a string with the name of the function, a
* callback method is an array of two elements: the element
* at index 0 is the object, and the element at index 1 is
* the name of the method to call in the object.
*
* When $mode is PEAR_ERROR_PRINT or PEAR_ERROR_DIE, this is
* a printf format string used when printing the error
* message.
*
* @access public
* @return void
* @see PEAR_ERROR_RETURN
* @see PEAR_ERROR_PRINT
* @see PEAR_ERROR_TRIGGER
* @see PEAR_ERROR_DIE
* @see PEAR_ERROR_CALLBACK
* @see PEAR_ERROR_EXCEPTION
*
* @since PHP 4.0.5
*/
protected static function _setErrorHandling(
$object, $mode = null, $options = null
) {
if ($object !== null) {
$setmode = &$object->_default_error_mode;
$setoptions = &$object->_default_error_options;
} else {
$setmode = &$GLOBALS['_PEAR_default_error_mode'];
$setoptions = &$GLOBALS['_PEAR_default_error_options'];
}
switch ($mode) {
case PEAR_ERROR_EXCEPTION:
case PEAR_ERROR_RETURN:
case PEAR_ERROR_PRINT:
case PEAR_ERROR_TRIGGER:
case PEAR_ERROR_DIE:
case null:
$setmode = $mode;
$setoptions = $options;
break;
case PEAR_ERROR_CALLBACK:
$setmode = $mode;
// class/object method callback
if (is_callable($options)) {
$setoptions = $options;
} else {
trigger_error("invalid error callback", E_USER_WARNING);
}
break;
default:
trigger_error("invalid error mode", E_USER_WARNING);
break;
}
}
/**
* This method is used to tell which errors you expect to get.
* Expected errors are always returned with error mode
* PEAR_ERROR_RETURN. Expected error codes are stored in a stack,
* and this method pushes a new element onto it. The list of
* expected errors are in effect until they are popped off the
* stack with the popExpect() method.
*
* Note that this method can not be called statically
*
* @param mixed $code a single error code or an array of error codes to expect
*
* @return int the new depth of the "expected errors" stack
* @access public
*/
function expectError($code = '*')
{
if (is_array($code)) {
array_push($this->_expected_errors, $code);
} else {
array_push($this->_expected_errors, array($code));
}
return count($this->_expected_errors);
}
/**
* This method pops one element off the expected error codes
* stack.
*
* @return array the list of error codes that were popped
*/
function popExpect()
{
return array_pop($this->_expected_errors);
}
/**
* This method checks unsets an error code if available
*
* @param mixed error code
* @return bool true if the error code was unset, false otherwise
* @access private
* @since PHP 4.3.0
*/
function _checkDelExpect($error_code)
{
$deleted = false;
foreach ($this->_expected_errors as $key => $error_array) {
if (in_array($error_code, $error_array)) {
unset($this->_expected_errors[$key][array_search($error_code, $error_array)]);
$deleted = true;
}
// clean up empty arrays
if (0 == count($this->_expected_errors[$key])) {
unset($this->_expected_errors[$key]);
}
}
return $deleted;
}
/**
* This method deletes all occurences of the specified element from
* the expected error codes stack.
*
* @param mixed $error_code error code that should be deleted
* @return mixed list of error codes that were deleted or error
* @access public
* @since PHP 4.3.0
*/
function delExpect($error_code)
{
$deleted = false;
if ((is_array($error_code) && (0 != count($error_code)))) {
// $error_code is a non-empty array here; we walk through it trying
// to unset all values
foreach ($error_code as $key => $error) {
$deleted = $this->_checkDelExpect($error) ? true : false;
}
return $deleted ? true : PEAR::raiseError("The expected error you submitted does not exist"); // IMPROVE ME
} elseif (!empty($error_code)) {
// $error_code comes alone, trying to unset it
if ($this->_checkDelExpect($error_code)) {
return true;
}
return PEAR::raiseError("The expected error you submitted does not exist"); // IMPROVE ME
}
// $error_code is empty
return PEAR::raiseError("The expected error you submitted is empty"); // IMPROVE ME
}
/**
* This method is a wrapper that returns an instance of the
* configured error class with this object's default error
* handling applied. If the $mode and $options parameters are not
* specified, the object's defaults are used.
*
* @param mixed $message a text error message or a PEAR error object
*
* @param int $code a numeric error code (it is up to your class
* to define these if you want to use codes)
*
* @param int $mode One of PEAR_ERROR_RETURN, PEAR_ERROR_PRINT,
* PEAR_ERROR_TRIGGER, PEAR_ERROR_DIE,
* PEAR_ERROR_CALLBACK, PEAR_ERROR_EXCEPTION.
*
* @param mixed $options If $mode is PEAR_ERROR_TRIGGER, this parameter
* specifies the PHP-internal error level (one of
* E_USER_NOTICE, E_USER_WARNING or E_USER_ERROR).
* If $mode is PEAR_ERROR_CALLBACK, this
* parameter specifies the callback function or
* method. In other error modes this parameter
* is ignored.
*
* @param string $userinfo If you need to pass along for example debug
* information, this parameter is meant for that.
*
* @param string $error_class The returned error object will be
* instantiated from this class, if specified.
*
* @param bool $skipmsg If true, raiseError will only pass error codes,
* the error message parameter will be dropped.
*
* @return object a PEAR error object
* @see PEAR::setErrorHandling
* @since PHP 4.0.5
*/
protected static function _raiseError($object,
$message = null,
$code = null,
$mode = null,
$options = null,
$userinfo = null,
$error_class = null,
$skipmsg = false)
{
// The error is yet a PEAR error object
if (is_object($message)) {
$code = $message->getCode();
$userinfo = $message->getUserInfo();
$error_class = $message->getType();
$message->error_message_prefix = '';
$message = $message->getMessage();
}
if (
$object !== null &&
isset($object->_expected_errors) &&
count($object->_expected_errors) > 0 &&
count($exp = end($object->_expected_errors))
) {
if ($exp[0] == "*" ||
(is_int(reset($exp)) && in_array($code, $exp)) ||
(is_string(reset($exp)) && in_array($message, $exp))
) {
$mode = PEAR_ERROR_RETURN;
}
}
// No mode given, try global ones
if ($mode === null) {
// Class error handler
if ($object !== null && isset($object->_default_error_mode)) {
$mode = $object->_default_error_mode;
$options = $object->_default_error_options;
// Global error handler
} elseif (isset($GLOBALS['_PEAR_default_error_mode'])) {
$mode = $GLOBALS['_PEAR_default_error_mode'];
$options = $GLOBALS['_PEAR_default_error_options'];
}
}
if ($error_class !== null) {
$ec = $error_class;
} elseif ($object !== null && isset($object->_error_class)) {
$ec = $object->_error_class;
} else {
$ec = 'PEAR_Error';
}
if ($skipmsg) {
$a = new $ec($code, $mode, $options, $userinfo);
} else {
$a = new $ec($message, $code, $mode, $options, $userinfo);
}
return $a;
}
/**
* Simpler form of raiseError with fewer options. In most cases
* message, code and userinfo are enough.
*
* @param mixed $message a text error message or a PEAR error object
*
* @param int $code a numeric error code (it is up to your class
* to define these if you want to use codes)
*
* @param string $userinfo If you need to pass along for example debug
* information, this parameter is meant for that.
*
* @return object a PEAR error object
* @see PEAR::raiseError
*/
protected static function _throwError($object, $message = null, $code = null, $userinfo = null)
{
if ($object !== null) {
$a = &$object->raiseError($message, $code, null, null, $userinfo);
return $a;
}
$a = &PEAR::raiseError($message, $code, null, null, $userinfo);
return $a;
}
public static function staticPushErrorHandling($mode, $options = null)
{
$stack = &$GLOBALS['_PEAR_error_handler_stack'];
$def_mode = &$GLOBALS['_PEAR_default_error_mode'];
$def_options = &$GLOBALS['_PEAR_default_error_options'];
$stack[] = array($def_mode, $def_options);
switch ($mode) {
case PEAR_ERROR_EXCEPTION:
case PEAR_ERROR_RETURN:
case PEAR_ERROR_PRINT:
case PEAR_ERROR_TRIGGER:
case PEAR_ERROR_DIE:
case null:
$def_mode = $mode;
$def_options = $options;
break;
case PEAR_ERROR_CALLBACK:
$def_mode = $mode;
// class/object method callback
if (is_callable($options)) {
$def_options = $options;
} else {
trigger_error("invalid error callback", E_USER_WARNING);
}
break;
default:
trigger_error("invalid error mode", E_USER_WARNING);
break;
}
$stack[] = array($mode, $options);
return true;
}
public static function staticPopErrorHandling()
{
$stack = &$GLOBALS['_PEAR_error_handler_stack'];
$setmode = &$GLOBALS['_PEAR_default_error_mode'];
$setoptions = &$GLOBALS['_PEAR_default_error_options'];
array_pop($stack);
list($mode, $options) = $stack[sizeof($stack) - 1];
array_pop($stack);
switch ($mode) {
case PEAR_ERROR_EXCEPTION:
case PEAR_ERROR_RETURN:
case PEAR_ERROR_PRINT:
case PEAR_ERROR_TRIGGER:
case PEAR_ERROR_DIE:
case null:
$setmode = $mode;
$setoptions = $options;
break;
case PEAR_ERROR_CALLBACK:
$setmode = $mode;
// class/object method callback
if (is_callable($options)) {
$setoptions = $options;
} else {
trigger_error("invalid error callback", E_USER_WARNING);
}
break;
default:
trigger_error("invalid error mode", E_USER_WARNING);
break;
}
return true;
}
/**
* Push a new error handler on top of the error handler options stack. With this
* you can easily override the actual error handler for some code and restore
* it later with popErrorHandling.
*
* @param mixed $mode (same as setErrorHandling)
* @param mixed $options (same as setErrorHandling)
*
* @return bool Always true
*
* @see PEAR::setErrorHandling
*/
protected static function _pushErrorHandling($object, $mode, $options = null)
{
$stack = &$GLOBALS['_PEAR_error_handler_stack'];
if ($object !== null) {
$def_mode = &$object->_default_error_mode;
$def_options = &$object->_default_error_options;
} else {
$def_mode = &$GLOBALS['_PEAR_default_error_mode'];
$def_options = &$GLOBALS['_PEAR_default_error_options'];
}
$stack[] = array($def_mode, $def_options);
if ($object !== null) {
$object->setErrorHandling($mode, $options);
} else {
PEAR::setErrorHandling($mode, $options);
}
$stack[] = array($mode, $options);
return true;
}
/**
* Pop the last error handler used
*
* @return bool Always true
*
* @see PEAR::pushErrorHandling
*/
protected static function _popErrorHandling($object)
{
$stack = &$GLOBALS['_PEAR_error_handler_stack'];
array_pop($stack);
list($mode, $options) = $stack[sizeof($stack) - 1];
array_pop($stack);
if ($object !== null) {
$object->setErrorHandling($mode, $options);
} else {
PEAR::setErrorHandling($mode, $options);
}
return true;
}
/**
* OS independent PHP extension load. Remember to take care
* on the correct extension name for case sensitive OSes.
*
* @param string $ext The extension name
* @return bool Success or not on the dl() call
*/
public static function loadExtension($ext)
{
if (extension_loaded($ext)) {
return true;
}
// if either returns true dl() will produce a FATAL error, stop that
if (
function_exists('dl') === false ||
ini_get('enable_dl') != 1
) {
return false;
}
if (OS_WINDOWS) {
$suffix = '.dll';
} elseif (PHP_OS == 'HP-UX') {
$suffix = '.sl';
} elseif (PHP_OS == 'AIX') {
$suffix = '.a';
} elseif (PHP_OS == 'OSX') {
$suffix = '.bundle';
} else {
$suffix = '.so';
}
return @dl('php_'.$ext.$suffix) || @dl($ext.$suffix);
}
}
function _PEAR_call_destructors()
{
global $_PEAR_destructor_object_list;
if (is_array($_PEAR_destructor_object_list) &&
sizeof($_PEAR_destructor_object_list))
{
reset($_PEAR_destructor_object_list);
$destructLifoExists = PEAR::getStaticProperty('PEAR', 'destructlifo');
if ($destructLifoExists) {
$_PEAR_destructor_object_list = array_reverse($_PEAR_destructor_object_list);
}
while (list($k, $objref) = each($_PEAR_destructor_object_list)) {
$classname = get_class($objref);
while ($classname) {
$destructor = "_$classname";
if (method_exists($objref, $destructor)) {
$objref->$destructor();
break;
} else {
$classname = get_parent_class($classname);
}
}
}
// Empty the object list to ensure that destructors are
// not called more than once.
$_PEAR_destructor_object_list = array();
}
// Now call the shutdown functions
if (
isset($GLOBALS['_PEAR_shutdown_funcs']) &&
is_array($GLOBALS['_PEAR_shutdown_funcs']) &&
!empty($GLOBALS['_PEAR_shutdown_funcs'])
) {
foreach ($GLOBALS['_PEAR_shutdown_funcs'] as $value) {
call_user_func_array($value[0], $value[1]);
}
}
}
/**
* Standard PEAR error class for PHP 4
*
* This class is supserseded by {@link PEAR_Exception} in PHP 5
*
* @category pear
* @package PEAR
* @author Stig Bakken <ssb@php.net>
* @author Tomas V.V. Cox <cox@idecnet.com>
* @author Gregory Beaver <cellog@php.net>
* @copyright 1997-2006 The PHP Group
* @license http://opensource.org/licenses/bsd-license.php New BSD License
* @version Release: @package_version@
* @link http://pear.php.net/manual/en/core.pear.pear-error.php
* @see PEAR::raiseError(), PEAR::throwError()
* @since Class available since PHP 4.0.2
*/
class PEAR_Error
{
var $error_message_prefix = '';
var $mode = PEAR_ERROR_RETURN;
var $level = E_USER_NOTICE;
var $code = -1;
var $message = '';
var $userinfo = '';
var $backtrace = null;
/**
* PEAR_Error constructor
*
* @param string $message message
*
* @param int $code (optional) error code
*
* @param int $mode (optional) error mode, one of: PEAR_ERROR_RETURN,
* PEAR_ERROR_PRINT, PEAR_ERROR_DIE, PEAR_ERROR_TRIGGER,
* PEAR_ERROR_CALLBACK or PEAR_ERROR_EXCEPTION
*
* @param mixed $options (optional) error level, _OR_ in the case of
* PEAR_ERROR_CALLBACK, the callback function or object/method
* tuple.
*
* @param string $userinfo (optional) additional user/debug info
*
* @access public
*
*/
function __construct($message = 'unknown error', $code = null,
$mode = null, $options = null, $userinfo = null)
{
if ($mode === null) {
$mode = PEAR_ERROR_RETURN;
}
$this->message = $message;
$this->code = $code;
$this->mode = $mode;
$this->userinfo = $userinfo;
$skiptrace = PEAR::getStaticProperty('PEAR_Error', 'skiptrace');
if (!$skiptrace) {
$this->backtrace = debug_backtrace();
if (isset($this->backtrace[0]) && isset($this->backtrace[0]['object'])) {
unset($this->backtrace[0]['object']);
}
}
if ($mode & PEAR_ERROR_CALLBACK) {
$this->level = E_USER_NOTICE;
$this->callback = $options;
} else {
if ($options === null) {
$options = E_USER_NOTICE;
}
$this->level = $options;
$this->callback = null;
}
if ($this->mode & PEAR_ERROR_PRINT) {
if (is_null($options) || is_int($options)) {
$format = "%s";
} else {
$format = $options;
}
printf($format, $this->getMessage());
}
if ($this->mode & PEAR_ERROR_TRIGGER) {
trigger_error($this->getMessage(), $this->level);
}
if ($this->mode & PEAR_ERROR_DIE) {
$msg = $this->getMessage();
if (is_null($options) || is_int($options)) {
$format = "%s";
if (substr($msg, -1) != "\n") {
$msg .= "\n";
}
} else {
$format = $options;
}
die(sprintf($format, $msg));
}
if ($this->mode & PEAR_ERROR_CALLBACK && is_callable($this->callback)) {
call_user_func($this->callback, $this);
}
if ($this->mode & PEAR_ERROR_EXCEPTION) {
trigger_error("PEAR_ERROR_EXCEPTION is obsolete, use class PEAR_Exception for exceptions", E_USER_WARNING);
eval('$e = new Exception($this->message, $this->code);throw($e);');
}
}
/**
* Only here for backwards compatibility.
*
* Class "Cache_Error" still uses it, among others.
*
* @param string $message Message
* @param int $code Error code
* @param int $mode Error mode
* @param mixed $options See __construct()
* @param string $userinfo Additional user/debug info
*/
public function PEAR_Error(
$message = 'unknown error', $code = null, $mode = null,
$options = null, $userinfo = null
) {
self::__construct($message, $code, $mode, $options, $userinfo);
}
/**
* Get the error mode from an error object.
*
* @return int error mode
* @access public
*/
function getMode()
{
return $this->mode;
}
/**
* Get the callback function/method from an error object.
*
* @return mixed callback function or object/method array
* @access public
*/
function getCallback()
{
return $this->callback;
}
/**
* Get the error message from an error object.
*
* @return string full error message
* @access public
*/
function getMessage()
{
return ($this->error_message_prefix . $this->message);
}
/**
* Get error code from an error object
*
* @return int error code
* @access public
*/
function getCode()
{
return $this->code;
}
/**
* Get the name of this error/exception.
*
* @return string error/exception name (type)
* @access public
*/
function getType()
{
return get_class($this);
}
/**
* Get additional user-supplied information.
*
* @return string user-supplied information
* @access public
*/
function getUserInfo()
{
return $this->userinfo;
}
/**
* Get additional debug information supplied by the application.
*
* @return string debug information
* @access public
*/
function getDebugInfo()
{
return $this->getUserInfo();
}
/**
* Get the call backtrace from where the error was generated.
* Supported with PHP 4.3.0 or newer.
*
* @param int $frame (optional) what frame to fetch
* @return array Backtrace, or NULL if not available.
* @access public
*/
function getBacktrace($frame = null)
{
if (defined('PEAR_IGNORE_BACKTRACE')) {
return null;
}
if ($frame === null) {
return $this->backtrace;
}
return $this->backtrace[$frame];
}
function addUserInfo($info)
{
if (empty($this->userinfo)) {
$this->userinfo = $info;
} else {
$this->userinfo .= " ** $info";
}
}
function __toString()
{
return $this->getMessage();
}
/**
* Make a string representation of this object.
*
* @return string a string with an object summary
* @access public
*/
function toString()
{
$modes = array();
$levels = array(E_USER_NOTICE => 'notice',
E_USER_WARNING => 'warning',
E_USER_ERROR => 'error');
if ($this->mode & PEAR_ERROR_CALLBACK) {
if (is_array($this->callback)) {
$callback = (is_object($this->callback[0]) ?
strtolower(get_class($this->callback[0])) :
$this->callback[0]) . '::' .
$this->callback[1];
} else {
$callback = $this->callback;
}
return sprintf('[%s: message="%s" code=%d mode=callback '.
'callback=%s prefix="%s" info="%s"]',
strtolower(get_class($this)), $this->message, $this->code,
$callback, $this->error_message_prefix,
$this->userinfo);
}
if ($this->mode & PEAR_ERROR_PRINT) {
$modes[] = 'print';
}
if ($this->mode & PEAR_ERROR_TRIGGER) {
$modes[] = 'trigger';
}
if ($this->mode & PEAR_ERROR_DIE) {
$modes[] = 'die';
}
if ($this->mode & PEAR_ERROR_RETURN) {
$modes[] = 'return';
}
return sprintf('[%s: message="%s" code=%d mode=%s level=%s '.
'prefix="%s" info="%s"]',
strtolower(get_class($this)), $this->message, $this->code,
implode("|", $modes), $levels[$this->level],
$this->error_message_prefix,
$this->userinfo);
}
}
/*
* Local Variables:
* mode: php
* tab-width: 4
* c-basic-offset: 4
* End:
*/
<?php
/**
* File/Directory manipulation
*
* PHP versions 4 and 5
*
* @category pear
* @package System
* @author Tomas V.V.Cox <cox@idecnet.com>
* @copyright 1997-2009 The Authors
* @license http://opensource.org/licenses/bsd-license.php New BSD License
* @link http://pear.php.net/package/PEAR
* @since File available since Release 0.1
*/
/**
* base class
*/
require_once 'PEAR.php';
require_once 'Console/Getopt.php';
$GLOBALS['_System_temp_files'] = array();
/**
* System offers cross plattform compatible system functions
*
* Static functions for different operations. Should work under
* Unix and Windows. The names and usage has been taken from its respectively
* GNU commands. The functions will return (bool) false on error and will
* trigger the error with the PHP trigger_error() function (you can silence
* the error by prefixing a '@' sign after the function call, but this
* is not recommended practice. Instead use an error handler with
* {@link set_error_handler()}).
*
* Documentation on this class you can find in:
* http://pear.php.net/manual/
*
* Example usage:
* if (!@System::rm('-r file1 dir1')) {
* print "could not delete file1 or dir1";
* }
*
* In case you need to to pass file names with spaces,
* pass the params as an array:
*
* System::rm(array('-r', $file1, $dir1));
*
* @category pear
* @package System
* @author Tomas V.V. Cox <cox@idecnet.com>
* @copyright 1997-2006 The PHP Group
* @license http://opensource.org/licenses/bsd-license.php New BSD License
* @version Release: @package_version@
* @link http://pear.php.net/package/PEAR
* @since Class available since Release 0.1
* @static
*/
class System
{
/**
* returns the commandline arguments of a function
*
* @param string $argv the commandline
* @param string $short_options the allowed option short-tags
* @param string $long_options the allowed option long-tags
* @return array the given options and there values
*/
public static function _parseArgs($argv, $short_options, $long_options = null)
{
if (!is_array($argv) && $argv !== null) {
/*
// Quote all items that are a short option
$av = preg_split('/(\A| )--?[a-z0-9]+[ =]?((?<!\\\\)((,\s*)|((?<!,)\s+))?)/i', $argv, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_OFFSET_CAPTURE);
$offset = 0;
foreach ($av as $a) {
$b = trim($a[0]);
if ($b{0} == '"' || $b{0} == "'") {
continue;
}
$escape = escapeshellarg($b);
$pos = $a[1] + $offset;
$argv = substr_replace($argv, $escape, $pos, strlen($b));
$offset += 2;
}
*/
// Find all items, quoted or otherwise
preg_match_all("/(?:[\"'])(.*?)(?:['\"])|([^\s]+)/", $argv, $av);
$argv = $av[1];
foreach ($av[2] as $k => $a) {
if (empty($a)) {
continue;
}
$argv[$k] = trim($a) ;
}
}
return Console_Getopt::getopt2($argv, $short_options, $long_options);
}
/**
* Output errors with PHP trigger_error(). You can silence the errors
* with prefixing a "@" sign to the function call: @System::mkdir(..);
*
* @param mixed $error a PEAR error or a string with the error message
* @return bool false
*/
protected static function raiseError($error)
{
if (PEAR::isError($error)) {
$error = $error->getMessage();
}
trigger_error($error, E_USER_WARNING);
return false;
}
/**
* Creates a nested array representing the structure of a directory
*
* System::_dirToStruct('dir1', 0) =>
* Array
* (
* [dirs] => Array
* (
* [0] => dir1
* )
*
* [files] => Array
* (
* [0] => dir1/file2
* [1] => dir1/file3
* )
* )
* @param string $sPath Name of the directory
* @param integer $maxinst max. deep of the lookup
* @param integer $aktinst starting deep of the lookup
* @param bool $silent if true, do not emit errors.
* @return array the structure of the dir
*/
protected static function _dirToStruct($sPath, $maxinst, $aktinst = 0, $silent = false)
{
$struct = array('dirs' => array(), 'files' => array());
if (($dir = @opendir($sPath)) === false) {
if (!$silent) {
System::raiseError("Could not open dir $sPath");
}
return $struct; // XXX could not open error
}
$struct['dirs'][] = $sPath = realpath($sPath); // XXX don't add if '.' or '..' ?
$list = array();
while (false !== ($file = readdir($dir))) {
if ($file != '.' && $file != '..') {
$list[] = $file;
}
}
closedir($dir);
natsort($list);
if ($aktinst < $maxinst || $maxinst == 0) {
foreach ($list as $val) {
$path = $sPath . DIRECTORY_SEPARATOR . $val;
if (is_dir($path) && !is_link($path)) {
$tmp = System::_dirToStruct($path, $maxinst, $aktinst+1, $silent);
$struct = array_merge_recursive($struct, $tmp);
} else {
$struct['files'][] = $path;
}
}
}
return $struct;
}
/**
* Creates a nested array representing the structure of a directory and files
*
* @param array $files Array listing files and dirs
* @return array
* @static
* @see System::_dirToStruct()
*/
protected static function _multipleToStruct($files)
{
$struct = array('dirs' => array(), 'files' => array());
settype($files, 'array');
foreach ($files as $file) {
if (is_dir($file) && !is_link($file)) {
$tmp = System::_dirToStruct($file, 0);
$struct = array_merge_recursive($tmp, $struct);
} else {
if (!in_array($file, $struct['files'])) {
$struct['files'][] = $file;
}
}
}
return $struct;
}
/**
* The rm command for removing files.
* Supports multiple files and dirs and also recursive deletes
*
* @param string $args the arguments for rm
* @return mixed PEAR_Error or true for success
* @static
* @access public
*/
public static function rm($args)
{
$opts = System::_parseArgs($args, 'rf'); // "f" does nothing but I like it :-)
if (PEAR::isError($opts)) {
return System::raiseError($opts);
}
foreach ($opts[0] as $opt) {
if ($opt[0] == 'r') {
$do_recursive = true;
}
}
$ret = true;
if (isset($do_recursive)) {
$struct = System::_multipleToStruct($opts[1]);
foreach ($struct['files'] as $file) {
if (!@unlink($file)) {
$ret = false;
}
}
rsort($struct['dirs']);
foreach ($struct['dirs'] as $dir) {
if (!@rmdir($dir)) {
$ret = false;
}
}
} else {
foreach ($opts[1] as $file) {
$delete = (is_dir($file)) ? 'rmdir' : 'unlink';
if (!@$delete($file)) {
$ret = false;
}
}
}
return $ret;
}
/**
* Make directories.
*
* The -p option will create parent directories
* @param string $args the name of the director(y|ies) to create
* @return bool True for success
*/
public static function mkDir($args)
{
$opts = System::_parseArgs($args, 'pm:');
if (PEAR::isError($opts)) {
return System::raiseError($opts);
}
$mode = 0777; // default mode
foreach ($opts[0] as $opt) {
if ($opt[0] == 'p') {
$create_parents = true;
} elseif ($opt[0] == 'm') {
// if the mode is clearly an octal number (starts with 0)
// convert it to decimal
if (strlen($opt[1]) && $opt[1]{0} == '0') {
$opt[1] = octdec($opt[1]);
} else {
// convert to int
$opt[1] += 0;
}
$mode = $opt[1];
}
}
$ret = true;
if (isset($create_parents)) {
foreach ($opts[1] as $dir) {
$dirstack = array();
while ((!file_exists($dir) || !is_dir($dir)) &&
$dir != DIRECTORY_SEPARATOR) {
array_unshift($dirstack, $dir);
$dir = dirname($dir);
}
while ($newdir = array_shift($dirstack)) {
if (!is_writeable(dirname($newdir))) {
$ret = false;
break;
}
if (!mkdir($newdir, $mode)) {
$ret = false;
}
}
}
} else {
foreach($opts[1] as $dir) {
if ((@file_exists($dir) || !is_dir($dir)) && !mkdir($dir, $mode)) {
$ret = false;
}
}
}
return $ret;
}
/**
* Concatenate files
*
* Usage:
* 1) $var = System::cat('sample.txt test.txt');
* 2) System::cat('sample.txt test.txt > final.txt');
* 3) System::cat('sample.txt test.txt >> final.txt');
*
* Note: as the class use fopen, urls should work also (test that)
*
* @param string $args the arguments
* @return boolean true on success
*/
public static function &cat($args)
{
$ret = null;
$files = array();
if (!is_array($args)) {
$args = preg_split('/\s+/', $args, -1, PREG_SPLIT_NO_EMPTY);
}
$count_args = count($args);
for ($i = 0; $i < $count_args; $i++) {
if ($args[$i] == '>') {
$mode = 'wb';
$outputfile = $args[$i+1];
break;
} elseif ($args[$i] == '>>') {
$mode = 'ab+';
$outputfile = $args[$i+1];
break;
} else {
$files[] = $args[$i];
}
}
$outputfd = false;
if (isset($mode)) {
if (!$outputfd = fopen($outputfile, $mode)) {
$err = System::raiseError("Could not open $outputfile");
return $err;
}
$ret = true;
}
foreach ($files as $file) {
if (!$fd = fopen($file, 'r')) {
System::raiseError("Could not open $file");
continue;
}
while ($cont = fread($fd, 2048)) {
if (is_resource($outputfd)) {
fwrite($outputfd, $cont);
} else {
$ret .= $cont;
}
}
fclose($fd);
}
if (is_resource($outputfd)) {
fclose($outputfd);
}
return $ret;
}
/**
* Creates temporary files or directories. This function will remove
* the created files when the scripts finish its execution.
*
* Usage:
* 1) $tempfile = System::mktemp("prefix");
* 2) $tempdir = System::mktemp("-d prefix");
* 3) $tempfile = System::mktemp();
* 4) $tempfile = System::mktemp("-t /var/tmp prefix");
*
* prefix -> The string that will be prepended to the temp name
* (defaults to "tmp").
* -d -> A temporary dir will be created instead of a file.
* -t -> The target dir where the temporary (file|dir) will be created. If
* this param is missing by default the env vars TMP on Windows or
* TMPDIR in Unix will be used. If these vars are also missing
* c:\windows\temp or /tmp will be used.
*
* @param string $args The arguments
* @return mixed the full path of the created (file|dir) or false
* @see System::tmpdir()
*/
public static function mktemp($args = null)
{
static $first_time = true;
$opts = System::_parseArgs($args, 't:d');
if (PEAR::isError($opts)) {
return System::raiseError($opts);
}
foreach ($opts[0] as $opt) {
if ($opt[0] == 'd') {
$tmp_is_dir = true;
} elseif ($opt[0] == 't') {
$tmpdir = $opt[1];
}
}
$prefix = (isset($opts[1][0])) ? $opts[1][0] : 'tmp';
if (!isset($tmpdir)) {
$tmpdir = System::tmpdir();
}
if (!System::mkDir(array('-p', $tmpdir))) {
return false;
}
$tmp = tempnam($tmpdir, $prefix);
if (isset($tmp_is_dir)) {
unlink($tmp); // be careful possible race condition here
if (!mkdir($tmp, 0700)) {
return System::raiseError("Unable to create temporary directory $tmpdir");
}
}
$GLOBALS['_System_temp_files'][] = $tmp;
if (isset($tmp_is_dir)) {
//$GLOBALS['_System_temp_files'][] = dirname($tmp);
}
if ($first_time) {
PEAR::registerShutdownFunc(array('System', '_removeTmpFiles'));
$first_time = false;
}
return $tmp;
}
/**
* Remove temporary files created my mkTemp. This function is executed
* at script shutdown time
*/
public static function _removeTmpFiles()
{
if (count($GLOBALS['_System_temp_files'])) {
$delete = $GLOBALS['_System_temp_files'];
array_unshift($delete, '-r');
System::rm($delete);
$GLOBALS['_System_temp_files'] = array();
}
}
/**
* Get the path of the temporal directory set in the system
* by looking in its environments variables.
* Note: php.ini-recommended removes the "E" from the variables_order setting,
* making unavaible the $_ENV array, that s why we do tests with _ENV
*
* @return string The temporary directory on the system
*/
public static function tmpdir()
{
if (OS_WINDOWS) {
if ($var = isset($_ENV['TMP']) ? $_ENV['TMP'] : getenv('TMP')) {
return $var;
}
if ($var = isset($_ENV['TEMP']) ? $_ENV['TEMP'] : getenv('TEMP')) {
return $var;
}
if ($var = isset($_ENV['USERPROFILE']) ? $_ENV['USERPROFILE'] : getenv('USERPROFILE')) {
return $var;
}
if ($var = isset($_ENV['windir']) ? $_ENV['windir'] : getenv('windir')) {
return $var;
}
return getenv('SystemRoot') . '\temp';
}
if ($var = isset($_ENV['TMPDIR']) ? $_ENV['TMPDIR'] : getenv('TMPDIR')) {
return $var;
}
return realpath('/tmp');
}
/**
* The "which" command (show the full path of a command)
*
* @param string $program The command to search for
* @param mixed $fallback Value to return if $program is not found
*
* @return mixed A string with the full path or false if not found
* @author Stig Bakken <ssb@php.net>
*/
public static function which($program, $fallback = false)
{
// enforce API
if (!is_string($program) || '' == $program) {
return $fallback;
}
// full path given
if (basename($program) != $program) {
$path_elements[] = dirname($program);
$program = basename($program);
} else {
$path = getenv('PATH');
if (!$path) {
$path = getenv('Path'); // some OSes are just stupid enough to do this
}
$path_elements = explode(PATH_SEPARATOR, $path);
}
if (OS_WINDOWS) {
$exe_suffixes = getenv('PATHEXT')
? explode(PATH_SEPARATOR, getenv('PATHEXT'))
: array('.exe','.bat','.cmd','.com');
// allow passing a command.exe param
if (strpos($program, '.') !== false) {
array_unshift($exe_suffixes, '');
}
} else {
$exe_suffixes = array('');
}
foreach ($exe_suffixes as $suff) {
foreach ($path_elements as $dir) {
$file = $dir . DIRECTORY_SEPARATOR . $program . $suff;
if (is_executable($file)) {
return $file;
}
}
}
return $fallback;
}
/**
* The "find" command
*
* Usage:
*
* System::find($dir);
* System::find("$dir -type d");
* System::find("$dir -type f");
* System::find("$dir -name *.php");
* System::find("$dir -name *.php -name *.htm*");
* System::find("$dir -maxdepth 1");
*
* Params implmented:
* $dir -> Start the search at this directory
* -type d -> return only directories
* -type f -> return only files
* -maxdepth <n> -> max depth of recursion
* -name <pattern> -> search pattern (bash style). Multiple -name param allowed
*
* @param mixed Either array or string with the command line
* @return array Array of found files
*/
public static function find($args)
{
if (!is_array($args)) {
$args = preg_split('/\s+/', $args, -1, PREG_SPLIT_NO_EMPTY);
}
$dir = realpath(array_shift($args));
if (!$dir) {
return array();
}
$patterns = array();
$depth = 0;
$do_files = $do_dirs = true;
$args_count = count($args);
for ($i = 0; $i < $args_count; $i++) {
switch ($args[$i]) {
case '-type':
if (in_array($args[$i+1], array('d', 'f'))) {
if ($args[$i+1] == 'd') {
$do_files = false;
} else {
$do_dirs = false;
}
}
$i++;
break;
case '-name':
$name = preg_quote($args[$i+1], '#');
// our magic characters ? and * have just been escaped,
// so now we change the escaped versions to PCRE operators
$name = strtr($name, array('\?' => '.', '\*' => '.*'));
$patterns[] = '('.$name.')';
$i++;
break;
case '-maxdepth':
$depth = $args[$i+1];
break;
}
}
$path = System::_dirToStruct($dir, $depth, 0, true);
if ($do_files && $do_dirs) {
$files = array_merge($path['files'], $path['dirs']);
} elseif ($do_dirs) {
$files = $path['dirs'];
} else {
$files = $path['files'];
}
if (count($patterns)) {
$dsq = preg_quote(DIRECTORY_SEPARATOR, '#');
$pattern = '#(^|'.$dsq.')'.implode('|', $patterns).'($|'.$dsq.')#';
$ret = array();
$files_count = count($files);
for ($i = 0; $i < $files_count; $i++) {
// only search in the part of the file below the current directory
$filepart = basename($files[$i]);
if (preg_match($pattern, $filepart)) {
$ret[] = $files[$i];
}
}
return $ret;
}
return $files;
}
}<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 foldmethod=marker: */
/**
* PEAR_Exception
*
* PHP version 5
*
* @category PEAR
* @package PEAR_Exception
* @author Tomas V. V. Cox <cox@idecnet.com>
* @author Hans Lellelid <hans@velum.net>
* @author Bertrand Mansion <bmansion@mamasam.com>
* @author Greg Beaver <cellog@php.net>
* @copyright 1997-2009 The Authors
* @license http://opensource.org/licenses/bsd-license.php New BSD License
* @link http://pear.php.net/package/PEAR_Exception
* @since File available since Release 1.0.0
*/
/**
* Base PEAR_Exception Class
*
* 1) Features:
*
* - Nestable exceptions (throw new PEAR_Exception($msg, $prev_exception))
* - Definable triggers, shot when exceptions occur
* - Pretty and informative error messages
* - Added more context info available (like class, method or cause)
* - cause can be a PEAR_Exception or an array of mixed
* PEAR_Exceptions/PEAR_ErrorStack warnings
* - callbacks for specific exception classes and their children
*
* 2) Ideas:
*
* - Maybe a way to define a 'template' for the output
*
* 3) Inherited properties from PHP Exception Class:
*
* protected $message
* protected $code
* protected $line
* protected $file
* private $trace
*
* 4) Inherited methods from PHP Exception Class:
*
* __clone
* __construct
* getMessage
* getCode
* getFile
* getLine
* getTraceSafe
* getTraceSafeAsString
* __toString
*
* 5) Usage example
*
* <code>
* require_once 'PEAR/Exception.php';
*
* class Test {
* function foo() {
* throw new PEAR_Exception('Error Message', ERROR_CODE);
* }
* }
*
* function myLogger($pear_exception) {
* echo $pear_exception->getMessage();
* }
* // each time a exception is thrown the 'myLogger' will be called
* // (its use is completely optional)
* PEAR_Exception::addObserver('myLogger');
* $test = new Test;
* try {
* $test->foo();
* } catch (PEAR_Exception $e) {
* print $e;
* }
* </code>
*
* @category PEAR
* @package PEAR_Exception
* @author Tomas V.V.Cox <cox@idecnet.com>
* @author Hans Lellelid <hans@velum.net>
* @author Bertrand Mansion <bmansion@mamasam.com>
* @author Greg Beaver <cellog@php.net>
* @copyright 1997-2009 The Authors
* @license http://opensource.org/licenses/bsd-license.php New BSD License
* @version Release: @package_version@
* @link http://pear.php.net/package/PEAR_Exception
* @since Class available since Release 1.0.0
*/
class PEAR_Exception extends Exception
{
const OBSERVER_PRINT = -2;
const OBSERVER_TRIGGER = -4;
const OBSERVER_DIE = -8;
protected $cause;
private static $_observers = array();
private static $_uniqueid = 0;
private $_trace;
/**
* Supported signatures:
* - PEAR_Exception(string $message);
* - PEAR_Exception(string $message, int $code);
* - PEAR_Exception(string $message, Exception $cause);
* - PEAR_Exception(string $message, Exception $cause, int $code);
* - PEAR_Exception(string $message, PEAR_Error $cause);
* - PEAR_Exception(string $message, PEAR_Error $cause, int $code);
* - PEAR_Exception(string $message, array $causes);
* - PEAR_Exception(string $message, array $causes, int $code);
*
* @param string $message exception message
* @param int|Exception|PEAR_Error|array|null $p2 exception cause
* @param int|null $p3 exception code or null
*/
public function __construct($message, $p2 = null, $p3 = null)
{
if (is_int($p2)) {
$code = $p2;
$this->cause = null;
} elseif (is_object($p2) || is_array($p2)) {
// using is_object allows both Exception and PEAR_Error
if (is_object($p2) && !($p2 instanceof Exception)) {
if (!class_exists('PEAR_Error') || !($p2 instanceof PEAR_Error)) {
throw new PEAR_Exception(
'exception cause must be Exception, ' .
'array, or PEAR_Error'
);
}
}
$code = $p3;
if (is_array($p2) && isset($p2['message'])) {
// fix potential problem of passing in a single warning
$p2 = array($p2);
}
$this->cause = $p2;
} else {
$code = null;
$this->cause = null;
}
parent::__construct($message, $code);
$this->signal();
}
/**
* Add an exception observer
*
* @param mixed $callback - A valid php callback, see php func is_callable()
* - A PEAR_Exception::OBSERVER_* constant
* - An array(const PEAR_Exception::OBSERVER_*,
* mixed $options)
* @param string $label The name of the observer. Use this if you want
* to remove it later with removeObserver()
*
* @return void
*/
public static function addObserver($callback, $label = 'default')
{
self::$_observers[$label] = $callback;
}
/**
* Remove an exception observer
*
* @param string $label Name of the observer
*
* @return void
*/
public static function removeObserver($label = 'default')
{
unset(self::$_observers[$label]);
}
/**
* Generate a unique ID for an observer
*
* @return int unique identifier for an observer
*/
public static function getUniqueId()
{
return self::$_uniqueid++;
}
/**
* Send a signal to all observers
*
* @return void
*/
protected function signal()
{
foreach (self::$_observers as $func) {
if (is_callable($func)) {
call_user_func($func, $this);
continue;
}
settype($func, 'array');
switch ($func[0]) {
case self::OBSERVER_PRINT :
$f = (isset($func[1])) ? $func[1] : '%s';
printf($f, $this->getMessage());
break;
case self::OBSERVER_TRIGGER :
$f = (isset($func[1])) ? $func[1] : E_USER_NOTICE;
trigger_error($this->getMessage(), $f);
break;
case self::OBSERVER_DIE :
$f = (isset($func[1])) ? $func[1] : '%s';
die(printf($f, $this->getMessage()));
break;
default:
trigger_error('invalid observer type', E_USER_WARNING);
}
}
}
/**
* Return specific error information that can be used for more detailed
* error messages or translation.
*
* This method may be overridden in child exception classes in order
* to add functionality not present in PEAR_Exception and is a placeholder
* to define API
*
* The returned array must be an associative array of parameter => value like so:
* <pre>
* array('name' => $name, 'context' => array(...))
* </pre>
*
* @return array
*/
public function getErrorData()
{
return array();
}
/**
* Returns the exception that caused this exception to be thrown
*
* @return Exception|array The context of the exception
*/
public function getCause()
{
return $this->cause;
}
/**
* Function must be public to call on caused exceptions
*
* @param array $causes Array that gets filled.
*
* @return void
*/
public function getCauseMessage(&$causes)
{
$trace = $this->getTraceSafe();
$cause = array('class' => get_class($this),
'message' => $this->message,
'file' => 'unknown',
'line' => 'unknown');
if (isset($trace[0])) {
if (isset($trace[0]['file'])) {
$cause['file'] = $trace[0]['file'];
$cause['line'] = $trace[0]['line'];
}
}
$causes[] = $cause;
if ($this->cause instanceof PEAR_Exception) {
$this->cause->getCauseMessage($causes);
} elseif ($this->cause instanceof Exception) {
$causes[] = array('class' => get_class($this->cause),
'message' => $this->cause->getMessage(),
'file' => $this->cause->getFile(),
'line' => $this->cause->getLine());
} elseif (class_exists('PEAR_Error') && $this->cause instanceof PEAR_Error) {
$causes[] = array('class' => get_class($this->cause),
'message' => $this->cause->getMessage(),
'file' => 'unknown',
'line' => 'unknown');
} elseif (is_array($this->cause)) {
foreach ($this->cause as $cause) {
if ($cause instanceof PEAR_Exception) {
$cause->getCauseMessage($causes);
} elseif ($cause instanceof Exception) {
$causes[] = array('class' => get_class($cause),
'message' => $cause->getMessage(),
'file' => $cause->getFile(),
'line' => $cause->getLine());
} elseif (class_exists('PEAR_Error')
&& $cause instanceof PEAR_Error
) {
$causes[] = array('class' => get_class($cause),
'message' => $cause->getMessage(),
'file' => 'unknown',
'line' => 'unknown');
} elseif (is_array($cause) && isset($cause['message'])) {
// PEAR_ErrorStack warning
$causes[] = array(
'class' => $cause['package'],
'message' => $cause['message'],
'file' => isset($cause['context']['file']) ?
$cause['context']['file'] :
'unknown',
'line' => isset($cause['context']['line']) ?
$cause['context']['line'] :
'unknown',
);
}
}
}
}
/**
* Build a backtrace and return it
*
* @return array Backtrace
*/
public function getTraceSafe()
{
if (!isset($this->_trace)) {
$this->_trace = $this->getTrace();
if (empty($this->_trace)) {
$backtrace = debug_backtrace();
$this->_trace = array($backtrace[count($backtrace)-1]);
}
}
return $this->_trace;
}
/**
* Gets the first class of the backtrace
*
* @return string Class name
*/
public function getErrorClass()
{
$trace = $this->getTraceSafe();
return $trace[0]['class'];
}
/**
* Gets the first method of the backtrace
*
* @return string Method/function name
*/
public function getErrorMethod()
{
$trace = $this->getTraceSafe();
return $trace[0]['function'];
}
/**
* Converts the exception to a string (HTML or plain text)
*
* @return string String representation
*
* @see toHtml()
* @see toText()
*/
public function __toString()
{
if (isset($_SERVER['REQUEST_URI'])) {
return $this->toHtml();
}
return $this->toText();
}
/**
* Generates a HTML representation of the exception
*
* @return string HTML code
*/
public function toHtml()
{
$trace = $this->getTraceSafe();
$causes = array();
$this->getCauseMessage($causes);
$html = '<table style="border: 1px" cellspacing="0">' . "\n";
foreach ($causes as $i => $cause) {
$html .= '<tr><td colspan="3" style="background: #ff9999">'
. str_repeat('-', $i) . ' <b>' . $cause['class'] . '</b>: '
. htmlspecialchars($cause['message'])
. ' in <b>' . $cause['file'] . '</b> '
. 'on line <b>' . $cause['line'] . '</b>'
. "</td></tr>\n";
}
$html .= '<tr><td colspan="3" style="background-color: #aaaaaa; text-align: center; font-weight: bold;">Exception trace</td></tr>' . "\n"
. '<tr><td style="text-align: center; background: #cccccc; width:20px; font-weight: bold;">#</td>'
. '<td style="text-align: center; background: #cccccc; font-weight: bold;">Function</td>'
. '<td style="text-align: center; background: #cccccc; font-weight: bold;">Location</td></tr>' . "\n";
foreach ($trace as $k => $v) {
$html .= '<tr><td style="text-align: center;">' . $k . '</td>'
. '<td>';
if (!empty($v['class'])) {
$html .= $v['class'] . $v['type'];
}
$html .= $v['function'];
$args = array();
if (!empty($v['args'])) {
foreach ($v['args'] as $arg) {
if (is_null($arg)) {
$args[] = 'null';
} else if (is_array($arg)) {
$args[] = 'Array';
} else if (is_object($arg)) {
$args[] = 'Object('.get_class($arg).')';
} else if (is_bool($arg)) {
$args[] = $arg ? 'true' : 'false';
} else if (is_int($arg) || is_double($arg)) {
$args[] = $arg;
} else {
$arg = (string)$arg;
$str = htmlspecialchars(substr($arg, 0, 16));
if (strlen($arg) > 16) {
$str .= '&hellip;';
}
$args[] = "'" . $str . "'";
}
}
}
$html .= '(' . implode(', ', $args) . ')'
. '</td>'
. '<td>' . (isset($v['file']) ? $v['file'] : 'unknown')
. ':' . (isset($v['line']) ? $v['line'] : 'unknown')
. '</td></tr>' . "\n";
}
$html .= '<tr><td style="text-align: center;">' . ($k+1) . '</td>'
. '<td>{main}</td>'
. '<td>&nbsp;</td></tr>' . "\n"
. '</table>';
return $html;
}
/**
* Generates text representation of the exception and stack trace
*
* @return string
*/
public function toText()
{
$causes = array();
$this->getCauseMessage($causes);
$causeMsg = '';
foreach ($causes as $i => $cause) {
$causeMsg .= str_repeat(' ', $i) . $cause['class'] . ': '
. $cause['message'] . ' in ' . $cause['file']
. ' on line ' . $cause['line'] . "\n";
}
return $causeMsg . $this->getTraceAsString();
}
}
?>
<?php
/**
* phpDocumentor
*
* PHP Version 5.5
*
* @copyright 2010-2015 Mike van Riel / Naenius (http://www.naenius.com)
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection;
/**
* Interface for Api Elements
*/
interface Element
{
/**
* Returns the Fqsen of the element.
*
* @return Fqsen
*/
public function getFqsen();
/**
* Returns the name of the element.
*
* @return string
*/
public function getName();
}<?php
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection;
/**
* Interface for files processed by the ProjectFactory
*/
interface File
{
/**
* Returns the content of the file as a string.
*
* @return string
*/
public function getContents();
/**
* Returns md5 hash of the file.
*
* @return string
*/
public function md5();
/**
* Returns an relative path to the file.
*
* @return string
*/
public function path();
}
<?php
/**
* phpDocumentor
*
* PHP Version 5.5
*
* @copyright 2010-2015 Mike van Riel / Naenius (http://www.naenius.com)
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection;
/**
* Value Object for Fqsen.
*
* @link https://github.com/phpDocumentor/fig-standards/blob/master/proposed/phpdoc-meta.md
*/
final class Fqsen
{
/**
* @var string full quallified class name
*/
private $fqsen;
/**
* @var string name of the element without path.
*/
private $name;
/**
* Initializes the object.
*
* @param string $fqsen
*
* @throws \InvalidArgumentException when $fqsen is not matching the format.
*/
public function __construct($fqsen)
{
$matches = array();
$result = preg_match('/^\\\\([\\w_\\\\]*)(?:[:]{2}\\$?([\\w_]+))?(?:\\(\\))?$/', $fqsen, $matches);
if ($result === 0) {
throw new \InvalidArgumentException(
sprintf('"%s" is not a valid Fqsen.', $fqsen)
);
}
$this->fqsen = $fqsen;
if (isset($matches[2])) {
$this->name = $matches[2];
} else {
$matches = explode('\\', $fqsen);
$this->name = trim(end($matches), '()');
}
}
/**
* converts this class to string.
*
* @return string
*/
public function __toString()
{
return $this->fqsen;
}
/**
* Returns the name of the element without path.
*
* @return string
*/
public function getName()
{
return $this->name;
}
}
<?php
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection;
/**
* The location where an element occurs within a file.
*/
final class Location
{
/** @var int */
private $lineNumber = 0;
/** @var int */
private $columnNumber = 0;
/**
* Initializes the location for an element using its line number in the file and optionally the column number.
*
* @param int $lineNumber
* @param int $columnNumber
*/
public function __construct($lineNumber, $columnNumber = 0)
{
$this->lineNumber = $lineNumber;
$this->columnNumber = $columnNumber;
}
/**
* Returns the line number that is covered by this location.
*
* @return integer
*/
public function getLineNumber()
{
return $this->lineNumber;
}
/**
* Returns the column number (character position on a line) for this location object.
*
* @return integer
*/
public function getColumnNumber()
{
return $this->columnNumber;
}
}
<?php
/**
* phpDocumentor
*
* PHP Version 5.5
*
* @copyright 2010-2015 Mike van Riel / Naenius (http://www.naenius.com)
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection;
/**
* Interface for project. Since the definition of a project can be different per factory this interface will be small.
*/
interface Project
{
/**
* Returns the name of the project.
*
* @return string
*/
public function getName();
}
<?php
/**
* phpDocumentor
*
* PHP Version 5.5
*
* @copyright 2010-2015 Mike van Riel / Naenius (http://www.naenius.com)
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection;
/**
* Interface for project factories. A project factory shall convert a set of files
* into an object implementing the Project interface.
*/
interface ProjectFactory
{
/**
* Creates a project from the set of files.
*
* @param string $name
* @param File[] $files
* @return Project
*/
public function create($name, array $files);
}
<?php
require_once(__DIR__ . '/../vendor/autoload.php');
use phpDocumentor\Reflection\DocBlockFactory;
$docComment = <<<DOCCOMMENT
/**
* This is an example of a summary.
*
* This is a Description. A Summary and Description are separated by either
* two subsequent newlines (thus a whiteline in between as can be seen in this
* example), or when the Summary ends with a dot (`.`) and some form of
* whitespace.
*/
DOCCOMMENT;
$factory = DocBlockFactory::createInstance();
$docblock = $factory->create($docComment);
// Should contain the first line of the DocBlock
$summary = $docblock->getSummary();
// Contains an object of type Description; you can either cast it to string or use
// the render method to get a string representation of the Description.
//
// In subsequent examples we will be fiddling a bit more with the Description.
$description = $docblock->getDescription();
<?php
require_once(__DIR__ . '/../vendor/autoload.php');
use phpDocumentor\Reflection\DocBlockFactory;
$docComment = <<<DOCCOMMENT
/**
* This is an example of a summary.
*
* @see \phpDocumentor\Reflection\DocBlock\StandardTagFactory
*/
DOCCOMMENT;
$factory = DocBlockFactory::createInstance();
$docblock = $factory->create($docComment);
// You can check if a DocBlock has one or more see tags
$hasSeeTag = $docblock->hasTag('see');
// Or we can get a complete list of all tags
$tags = $docblock->getTags();
// But we can also grab all tags of a specific type, such as `see`
$seeTags = $docblock->getTagsByName('see');
<?php
require_once(__DIR__ . '/../vendor/autoload.php');
use phpDocumentor\Reflection\DocBlock\Serializer;
use phpDocumentor\Reflection\DocBlockFactory;
$docComment = <<<DOCCOMMENT
/**
* This is an example of a summary.
*
* And here is an example of the description
* of a DocBlock that can span multiple lines.
*
* @see \phpDocumentor\Reflection\DocBlock\StandardTagFactory
*/
DOCCOMMENT;
$factory = DocBlockFactory::createInstance();
$docblock = $factory->create($docComment);
// Create the serializer that will reconstitute the DocBlock back to its original form.
$serializer = new Serializer();
// Reconstitution is performed by the `getDocComment()` method.
$reconstitutedDocComment = $serializer->getDocComment($docblock);
<?php
/**
* In this example we demonstrate how you can add your own Tag using a Static Factory method in your Tag class.
*/
require_once(__DIR__ . '/../vendor/autoload.php');
use phpDocumentor\Reflection\DocBlock\Serializer;
use phpDocumentor\Reflection\DocBlock\Tags\Factory\StaticMethod;
use phpDocumentor\Reflection\DocBlockFactory;
use phpDocumentor\Reflection\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\DescriptionFactory;
use phpDocumentor\Reflection\DocBlock\Tags\BaseTag;
use phpDocumentor\Reflection\Types\Context;
use Webmozart\Assert\Assert;
/**
* An example of a custom tag called `my-tag` with an optional description.
*
* A Custom Tag is a class that can consist of two parts:
*
* 1. a method `create` that is a static factory for this class.
* 2. methods and properties that have this object act as an immutable Value Object representing a Tag instance.
*
* The static factory `create` is used to convert a tag line (without the tag name) into an instance of the
* same tag object with the right constructor parameters set. This method has a dynamic list of parameters so that you
* can inject various dependencies, see the method's DocBlock for more information.
*
* An object of this class, and its methods and properties, represent a single instance of that tag in your
* documentation in the form of a Value Object whose properties should not be changed after instantiation (it should be
* immutable).
*
* > Important: Tag classes that act as Factories using the `create` method should implement the TagFactory interface.
*/
final class MyTag extends BaseTag implements StaticMethod
{
/**
* A required property that is used by Formatters to reconstitute the complete tag line.
*
* @see Formatter
*
* @var string
*/
protected $name = 'my-tag';
/**
* The constructor for this Tag; this should contain all properties for this object.
*
* @param Description $description An example of how to add a Description to the tag; the Description is often
* an optional variable so passing null is allowed in this instance (though you can
* also construct an empty description object).
*
* @see BaseTag for the declaration of the description property and getDescription method.
*/
public function __construct(Description $description = null)
{
$this->description = $description;
}
/**
* A static Factory that creates a new instance of the current Tag.
*
* In this example the MyTag tag can be created by passing a description text as $body. Because we have added
* a $descriptionFactory that is type-hinted as DescriptionFactory we can now construct a new Description object
* and pass that to the constructor.
*
* > You could directly instantiate a Description object here but that won't be parsed for inline tags and Types
* > won't be resolved. The DescriptionFactory will take care of those actions.
*
* The `create` method's interface states that this method only features a single parameter (`$body`) but the
* {@see TagFactory} will read the signature of this method and if it has more parameters then it will try
* to find declarations for it in the ServiceLocator of the TagFactory (see {@see TagFactory::$serviceLocator}).
*
* > Important: all properties following the `$body` should default to `null`, otherwise PHP will error because
* > it no longer matches the interface. This is why you often see the default tags check that an optional argument
* > is not null nonetheless.
*
* @param string $body
* @param DescriptionFactory $descriptionFactory
* @param Context|null $context The Context is used to resolve Types and FQSENs, although optional
* it is highly recommended to pass it. If you omit it then it is assumed that
* the DocBlock is in the global namespace and has no `use` statements.
*
* @see Tag for the interface declaration of the `create` method.
* @see Tag::create() for more information on this method's workings.
*
* @return MyTag
*/
public static function create($body, DescriptionFactory $descriptionFactory = null, Context $context = null)
{
Assert::string($body);
Assert::notNull($descriptionFactory);
return new static($descriptionFactory->create($body, $context));
}
/**
* Returns a rendition of the original tag line.
*
* This method is used to reconstitute a DocBlock into its original form by the {@see Serializer}. It should
* feature all parts of the tag so that the serializer can put it back together.
*
* @return string
*/
public function __toString()
{
return (string)$this->description;
}
}
$docComment = <<<DOCCOMMENT
/**
* This is an example of a summary.
*
* @my-tag I have a description
*/
DOCCOMMENT;
// Make a mapping between the tag name `my-tag` and the Tag class containing the Factory Method `create`.
$customTags = ['my-tag' => MyTag::class];
// Do pass the list of custom tags to the Factory for the DocBlockFactory.
$factory = DocBlockFactory::createInstance($customTags);
// You can also add Tags later using `$factory->registerTagHandler()` with a tag name and Tag class name.
// Create the DocBlock
$docblock = $factory->create($docComment);
// Take a look: the $customTagObjects now contain an array with your newly added tag
$customTagObjects = $docblock->getTagsByName('my-tag');
// As an experiment: let's reconstitute the DocBlock and observe that because we added a __toString() method
// to the tag class that we can now also see it.
$serializer = new Serializer();
$reconstitutedDocComment = $serializer->getDocComment($docblock);
<?php
require_once(__DIR__ . '/../../vendor/autoload.php');
use phpDocumentor\Reflection\DocBlockFactory;
$docComment = <<<DOCCOMMENT
/**
* This is an example of a summary.
*
* You can escape the @-sign by surrounding it with braces, for example: {@}. And escape a closing brace within an
* inline tag by adding an opening brace in front of it like this: {}.
*
* Here are example texts where you can see how they could be used in a real life situation:
*
* This is a text with an {@internal inline tag where a closing brace ({}) is shown}.
* Or an {@internal inline tag with a literal {{@}link{} in it}.
*
* Do note that an {@internal inline tag that has an opening brace ({) does not break out}.
*/
DOCCOMMENT;
$factory = DocBlockFactory::createInstance();
$docblock = $factory->create($docComment);
// Escaping is automatic so this happens in the DescriptionFactory.
$description = $docblock->getDescription();
// This is the rendition that we will receive of the Description.
$receivedDocComment = <<<DOCCOMMENT
/**
* This is an example of a summary.
*
* You can escape the @-sign by surrounding it with braces, for example: {@}. And escape a closing brace within an
* inline tag by adding an opening brace in front of it like this: {}.
*
* Here are example texts where you can see how they could be used in a real life situation:
*
* This is a text with an {@internal inline tag where a closing brace ({}) is shown}.
* Or an {@internal inline tag with a literal {{@}link{} in it}.
*
* Do note that an {@internal inline tag that has an opening brace ({) does not break out}.
*/
DOCCOMMENT;
// Render it using the default PassthroughFormatter
$foundDescription = $description->render();
<?php
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\DocBlock;
use phpDocumentor\Reflection\DocBlock\Tags\Formatter;
use phpDocumentor\Reflection\DocBlock\Tags\Formatter\PassthroughFormatter;
use Webmozart\Assert\Assert;
/**
* Object representing to description for a DocBlock.
*
* A Description object can consist of plain text but can also include tags. A Description Formatter can then combine
* a body template with sprintf-style placeholders together with formatted tags in order to reconstitute a complete
* description text using the format that you would prefer.
*
* Because parsing a Description text can be a verbose process this is handled by the {@see DescriptionFactory}. It is
* thus recommended to use that to create a Description object, like this:
*
* $description = $descriptionFactory->create('This is a {@see Description}', $context);
*
* The description factory will interpret the given body and create a body template and list of tags from them, and pass
* that onto the constructor if this class.
*
* > The $context variable is a class of type {@see \phpDocumentor\Reflection\Types\Context} and contains the namespace
* > and the namespace aliases that apply to this DocBlock. These are used by the Factory to resolve and expand partial
* > type names and FQSENs.
*
* If you do not want to use the DescriptionFactory you can pass a body template and tag listing like this:
*
* $description = new Description(
* 'This is a %1$s',
* [ new See(new Fqsen('\phpDocumentor\Reflection\DocBlock\Description')) ]
* );
*
* It is generally recommended to use the Factory as that will also apply escaping rules, while the Description object
* is mainly responsible for rendering.
*
* @see DescriptionFactory to create a new Description.
* @see Description\Formatter for the formatting of the body and tags.
*/
class Description
{
/** @var string */
private $bodyTemplate;
/** @var Tag[] */
private $tags;
/**
* Initializes a Description with its body (template) and a listing of the tags used in the body template.
*
* @param string $bodyTemplate
* @param Tag[] $tags
*/
public function __construct($bodyTemplate, array $tags = [])
{
Assert::string($bodyTemplate);
$this->bodyTemplate = $bodyTemplate;
$this->tags = $tags;
}
/**
* Renders this description as a string where the provided formatter will format the tags in the expected string
* format.
*
* @param Formatter|null $formatter
*
* @return string
*/
public function render(Formatter $formatter = null)
{
if ($formatter === null) {
$formatter = new PassthroughFormatter();
}
$tags = [];
foreach ($this->tags as $tag) {
$tags[] = '{' . $formatter->format($tag) . '}';
}
return vsprintf($this->bodyTemplate, $tags);
}
/**
* Returns a plain string representation of this description.
*
* @return string
*/
public function __toString()
{
return $this->render();
}
}
<?php
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\DocBlock;
use phpDocumentor\Reflection\Types\Context as TypeContext;
/**
* Creates a new Description object given a body of text.
*
* Descriptions in phpDocumentor are somewhat complex entities as they can contain one or more tags inside their
* body that can be replaced with a readable output. The replacing is done by passing a Formatter object to the
* Description object's `render` method.
*
* In addition to the above does a Description support two types of escape sequences:
*
* 1. `{@}` to escape the `@` character to prevent it from being interpreted as part of a tag, i.e. `{{@}link}`
* 2. `{}` to escape the `}` character, this can be used if you want to use the `}` character in the description
* of an inline tag.
*
* If a body consists of multiple lines then this factory will also remove any superfluous whitespace at the beginning
* of each line while maintaining any indentation that is used. This will prevent formatting parsers from tripping
* over unexpected spaces as can be observed with tag descriptions.
*/
class DescriptionFactory
{
/** @var TagFactory */
private $tagFactory;
/**
* Initializes this factory with the means to construct (inline) tags.
*
* @param TagFactory $tagFactory
*/
public function __construct(TagFactory $tagFactory)
{
$this->tagFactory = $tagFactory;
}
/**
* Returns the parsed text of this description.
*
* @param string $contents
* @param TypeContext $context
*
* @return Description
*/
public function create($contents, TypeContext $context = null)
{
list($text, $tags) = $this->parse($this->lex($contents), $context);
return new Description($text, $tags);
}
/**
* Strips the contents from superfluous whitespace and splits the description into a series of tokens.
*
* @param string $contents
*
* @return string[] A series of tokens of which the description text is composed.
*/
private function lex($contents)
{
$contents = $this->removeSuperfluousStartingWhitespace($contents);
// performance optimalization; if there is no inline tag, don't bother splitting it up.
if (strpos($contents, '{@') === false) {
return [$contents];
}
return preg_split(
'/\{
# "{@}" is not a valid inline tag. This ensures that we do not treat it as one, but treat it literally.
(?!@\})
# We want to capture the whole tag line, but without the inline tag delimiters.
(\@
# Match everything up to the next delimiter.
[^{}]*
# Nested inline tag content should not be captured, or it will appear in the result separately.
(?:
# Match nested inline tags.
(?:
# Because we did not catch the tag delimiters earlier, we must be explicit with them here.
# Notice that this also matches "{}", as a way to later introduce it as an escape sequence.
\{(?1)?\}
|
# Make sure we match hanging "{".
\{
)
# Match content after the nested inline tag.
[^{}]*
)* # If there are more inline tags, match them as well. We use "*" since there may not be any
# nested inline tags.
)
\}/Sux',
$contents,
null,
PREG_SPLIT_DELIM_CAPTURE
);
}
/**
* Parses the stream of tokens in to a new set of tokens containing Tags.
*
* @param string[] $tokens
* @param TypeContext $context
*
* @return string[]|Tag[]
*/
private function parse($tokens, TypeContext $context)
{
$count = count($tokens);
$tagCount = 0;
$tags = [];
for ($i = 1; $i < $count; $i += 2) {
$tags[] = $this->tagFactory->create($tokens[$i], $context);
$tokens[$i] = '%' . ++$tagCount . '$s';
}
//In order to allow "literal" inline tags, the otherwise invalid
//sequence "{@}" is changed to "@", and "{}" is changed to "}".
//"%" is escaped to "%%" because of vsprintf.
//See unit tests for examples.
for ($i = 0; $i < $count; $i += 2) {
$tokens[$i] = str_replace(['{@}', '{}', '%'], ['@', '}', '%%'], $tokens[$i]);
}
return [implode('', $tokens), $tags];
}
/**
* Removes the superfluous from a multi-line description.
*
* When a description has more than one line then it can happen that the second and subsequent lines have an
* additional indentation. This is commonly in use with tags like this:
*
* {@}since 1.1.0 This is an example
* description where we have an
* indentation in the second and
* subsequent lines.
*
* If we do not normalize the indentation then we have superfluous whitespace on the second and subsequent
* lines and this may cause rendering issues when, for example, using a Markdown converter.
*
* @param string $contents
*
* @return string
*/
private function removeSuperfluousStartingWhitespace($contents)
{
$lines = explode("\n", $contents);
// if there is only one line then we don't have lines with superfluous whitespace and
// can use the contents as-is
if (count($lines) <= 1) {
return $contents;
}
// determine how many whitespace characters need to be stripped
$startingSpaceCount = 9999999;
for ($i = 1; $i < count($lines); $i++) {
// lines with a no length do not count as they are not indented at all
if (strlen(trim($lines[$i])) === 0) {
continue;
}
// determine the number of prefixing spaces by checking the difference in line length before and after
// an ltrim
$startingSpaceCount = min($startingSpaceCount, strlen($lines[$i]) - strlen(ltrim($lines[$i])));
}
// strip the number of spaces from each line
if ($startingSpaceCount > 0) {
for ($i = 1; $i < count($lines); $i++) {
$lines[$i] = substr($lines[$i], $startingSpaceCount);
}
}
return implode("\n", $lines);
}
}
<?php
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection;
use phpDocumentor\Reflection\DocBlock\Tags\Example;
/**
* Class used to find an example file's location based on a given ExampleDescriptor.
*/
class ExampleFinder
{
/** @var string */
private $sourceDirectory = '';
/** @var string[] */
private $exampleDirectories = array();
/**
* Attempts to find the example contents for the given descriptor.
*
* @param Example $example
*
* @return string
*/
public function find(Example $example)
{
$filename = $example->getFilePath();
$file = $this->getExampleFileContents($filename);
if (!$file) {
return "** File not found : {$filename} **";
}
return implode('', array_slice($file, $example->getStartingLine() - 1, $example->getLineCount()));
}
/**
* Registers the project's root directory where an 'examples' folder can be expected.
*
* @param string $directory
*
* @return void
*/
public function setSourceDirectory($directory = '')
{
$this->sourceDirectory = $directory;
}
/**
* Returns the project's root directory where an 'examples' folder can be expected.
*
* @return string
*/
public function getSourceDirectory()
{
return $this->sourceDirectory;
}
/**
* Registers a series of directories that may contain examples.
*
* @param string[] $directories
*/
public function setExampleDirectories(array $directories)
{
$this->exampleDirectories = $directories;
}
/**
* Returns a series of directories that may contain examples.
*
* @return string[]
*/
public function getExampleDirectories()
{
return $this->exampleDirectories;
}
/**
* Attempts to find the requested example file and returns its contents or null if no file was found.
*
* This method will try several methods in search of the given example file, the first one it encounters is
* returned:
*
* 1. Iterates through all examples folders for the given filename
* 2. Checks the source folder for the given filename
* 3. Checks the 'examples' folder in the current working directory for examples
* 4. Checks the path relative to the current working directory for the given filename
*
* @param string $filename
*
* @return string|null
*/
private function getExampleFileContents($filename)
{
$normalizedPath = null;
foreach ($this->exampleDirectories as $directory) {
$exampleFileFromConfig = $this->constructExamplePath($directory, $filename);
if (is_readable($exampleFileFromConfig)) {
$normalizedPath = $exampleFileFromConfig;
break;
}
}
if (!$normalizedPath) {
if (is_readable($this->getExamplePathFromSource($filename))) {
$normalizedPath = $this->getExamplePathFromSource($filename);
} elseif (is_readable($this->getExamplePathFromExampleDirectory($filename))) {
$normalizedPath = $this->getExamplePathFromExampleDirectory($filename);
} elseif (is_readable($filename)) {
$normalizedPath = $filename;
}
}
return $normalizedPath && is_readable($normalizedPath) ? file($normalizedPath) : null;
}
/**
* Get example filepath based on the example directory inside your project.
*
* @param string $file
*
* @return string
*/
private function getExamplePathFromExampleDirectory($file)
{
return getcwd() . DIRECTORY_SEPARATOR . 'examples' . DIRECTORY_SEPARATOR . $file;
}
/**
* Returns a path to the example file in the given directory..
*
* @param string $directory
* @param string $file
*
* @return string
*/
private function constructExamplePath($directory, $file)
{
return rtrim($directory, '\\/') . DIRECTORY_SEPARATOR . $file;
}
/**
* Get example filepath based on sourcecode.
*
* @param string $file
*
* @return string
*/
private function getExamplePathFromSource($file)
{
return sprintf(
'%s%s%s',
trim($this->getSourceDirectory(), '\\/'),
DIRECTORY_SEPARATOR,
trim($file, '"')
);
}
}
<?php
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\DocBlock;
use phpDocumentor\Reflection\DocBlock;
use Webmozart\Assert\Assert;
/**
* Converts a DocBlock back from an object to a complete DocComment including Asterisks.
*/
class Serializer
{
/** @var string The string to indent the comment with. */
protected $indentString = ' ';
/** @var int The number of times the indent string is repeated. */
protected $indent = 0;
/** @var bool Whether to indent the first line with the given indent amount and string. */
protected $isFirstLineIndented = true;
/** @var int|null The max length of a line. */
protected $lineLength = null;
/**
* Create a Serializer instance.
*
* @param int $indent The number of times the indent string is repeated.
* @param string $indentString The string to indent the comment with.
* @param bool $indentFirstLine Whether to indent the first line.
* @param int|null $lineLength The max length of a line or NULL to disable line wrapping.
*/
public function __construct($indent = 0, $indentString = ' ', $indentFirstLine = true, $lineLength = null)
{
Assert::integer($indent);
Assert::string($indentString);
Assert::boolean($indentFirstLine);
Assert::nullOrInteger($lineLength);
$this->indent = $indent;
$this->indentString = $indentString;
$this->isFirstLineIndented = $indentFirstLine;
$this->lineLength = $lineLength;
}
/**
* Generate a DocBlock comment.
*
* @param DocBlock $docblock The DocBlock to serialize.
*
* @return string The serialized doc block.
*/
public function getDocComment(DocBlock $docblock)
{
$indent = str_repeat($this->indentString, $this->indent);
$firstIndent = $this->isFirstLineIndented ? $indent : '';
// 3 === strlen(' * ')
$wrapLength = $this->lineLength ? $this->lineLength - strlen($indent) - 3 : null;
$text = $this->removeTrailingSpaces(
$indent,
$this->addAsterisksForEachLine(
$indent,
$this->getSummaryAndDescriptionTextBlock($docblock, $wrapLength)
)
);
$comment = "{$firstIndent}/**\n{$indent} * {$text}\n{$indent} *\n";
$comment = $this->addTagBlock($docblock, $wrapLength, $indent, $comment);
$comment .= $indent . ' */';
return $comment;
}
/**
* @param $indent
* @param $text
* @return mixed
*/
private function removeTrailingSpaces($indent, $text)
{
return str_replace("\n{$indent} * \n", "\n{$indent} *\n", $text);
}
/**
* @param $indent
* @param $text
* @return mixed
*/
private function addAsterisksForEachLine($indent, $text)
{
return str_replace("\n", "\n{$indent} * ", $text);
}
/**
* @param DocBlock $docblock
* @param $wrapLength
* @return string
*/
private function getSummaryAndDescriptionTextBlock(DocBlock $docblock, $wrapLength)
{
$text = $docblock->getSummary() . ((string)$docblock->getDescription() ? "\n\n" . $docblock->getDescription()
: '');
if ($wrapLength !== null) {
$text = wordwrap($text, $wrapLength);
return $text;
}
return $text;
}
/**
* @param DocBlock $docblock
* @param $wrapLength
* @param $indent
* @param $comment
* @return string
*/
private function addTagBlock(DocBlock $docblock, $wrapLength, $indent, $comment)
{
foreach ($docblock->getTags() as $tag) {
$formatter = new DocBlock\Tags\Formatter\PassthroughFormatter();
$tagText = $formatter->format($tag);
if ($wrapLength !== null) {
$tagText = wordwrap($tagText, $wrapLength);
}
$tagText = str_replace("\n", "\n{$indent} * ", $tagText);
$comment .= "{$indent} * {$tagText}\n";
}
return $comment;
}
}
<?php
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\DocBlock;
use phpDocumentor\Reflection\DocBlock\Tags\Factory\StaticMethod;
use phpDocumentor\Reflection\DocBlock\Tags\Generic;
use phpDocumentor\Reflection\FqsenResolver;
use phpDocumentor\Reflection\Types\Context as TypeContext;
use Webmozart\Assert\Assert;
/**
* Creates a Tag object given the contents of a tag.
*
* This Factory is capable of determining the appropriate class for a tag and instantiate it using its `create`
* factory method. The `create` factory method of a Tag can have a variable number of arguments; this way you can
* pass the dependencies that you need to construct a tag object.
*
* > Important: each parameter in addition to the body variable for the `create` method must default to null, otherwise
* > it violates the constraint with the interface; it is recommended to use the {@see Assert::notNull()} method to
* > verify that a dependency is actually passed.
*
* This Factory also features a Service Locator component that is used to pass the right dependencies to the
* `create` method of a tag; each dependency should be registered as a service or as a parameter.
*
* When you want to use a Tag of your own with custom handling you need to call the `registerTagHandler` method, pass
* the name of the tag and a Fully Qualified Class Name pointing to a class that implements the Tag interface.
*/
final class StandardTagFactory implements TagFactory
{
/** PCRE regular expression matching a tag name. */
const REGEX_TAGNAME = '[\w\-\_\\\\]+';
/**
* @var string[] An array with a tag as a key, and an FQCN to a class that handles it as an array value.
*/
private $tagHandlerMappings = [
'author' => '\phpDocumentor\Reflection\DocBlock\Tags\Author',
'covers' => '\phpDocumentor\Reflection\DocBlock\Tags\Covers',
'deprecated' => '\phpDocumentor\Reflection\DocBlock\Tags\Deprecated',
// 'example' => '\phpDocumentor\Reflection\DocBlock\Tags\Example',
'link' => '\phpDocumentor\Reflection\DocBlock\Tags\Link',
'method' => '\phpDocumentor\Reflection\DocBlock\Tags\Method',
'param' => '\phpDocumentor\Reflection\DocBlock\Tags\Param',
'property-read' => '\phpDocumentor\Reflection\DocBlock\Tags\PropertyRead',
'property' => '\phpDocumentor\Reflection\DocBlock\Tags\Property',
'property-write' => '\phpDocumentor\Reflection\DocBlock\Tags\PropertyWrite',
'return' => '\phpDocumentor\Reflection\DocBlock\Tags\Return_',
'see' => '\phpDocumentor\Reflection\DocBlock\Tags\See',
'since' => '\phpDocumentor\Reflection\DocBlock\Tags\Since',
'source' => '\phpDocumentor\Reflection\DocBlock\Tags\Source',
'throw' => '\phpDocumentor\Reflection\DocBlock\Tags\Throws',
'throws' => '\phpDocumentor\Reflection\DocBlock\Tags\Throws',
'uses' => '\phpDocumentor\Reflection\DocBlock\Tags\Uses',
'var' => '\phpDocumentor\Reflection\DocBlock\Tags\Var_',
'version' => '\phpDocumentor\Reflection\DocBlock\Tags\Version'
];
/**
* @var \ReflectionParameter[][] a lazy-loading cache containing parameters for each tagHandler that has been used.
*/
private $tagHandlerParameterCache = [];
/**
* @var FqsenResolver
*/
private $fqsenResolver;
/**
* @var mixed[] an array representing a simple Service Locator where we can store parameters and
* services that can be inserted into the Factory Methods of Tag Handlers.
*/
private $serviceLocator = [];
/**
* Initialize this tag factory with the means to resolve an FQSEN and optionally a list of tag handlers.
*
* If no tag handlers are provided than the default list in the {@see self::$tagHandlerMappings} property
* is used.
*
* @param FqsenResolver $fqsenResolver
* @param string[] $tagHandlers
*
* @see self::registerTagHandler() to add a new tag handler to the existing default list.
*/
public function __construct(FqsenResolver $fqsenResolver, array $tagHandlers = null)
{
$this->fqsenResolver = $fqsenResolver;
if ($tagHandlers !== null) {
$this->tagHandlerMappings = $tagHandlers;
}
$this->addService($fqsenResolver, FqsenResolver::class);
}
/**
* {@inheritDoc}
*/
public function create($tagLine, TypeContext $context = null)
{
if (! $context) {
$context = new TypeContext('');
}
list($tagName, $tagBody) = $this->extractTagParts($tagLine);
return $this->createTag($tagBody, $tagName, $context);
}
/**
* {@inheritDoc}
*/
public function addParameter($name, $value)
{
$this->serviceLocator[$name] = $value;
}
/**
* {@inheritDoc}
*/
public function addService($service, $alias = null)
{
$this->serviceLocator[$alias ?: get_class($service)] = $service;
}
/**
* {@inheritDoc}
*/
public function registerTagHandler($tagName, $handler)
{
Assert::stringNotEmpty($tagName);
Assert::stringNotEmpty($handler);
Assert::classExists($handler);
Assert::implementsInterface($handler, StaticMethod::class);
if (strpos($tagName, '\\') && $tagName[0] !== '\\') {
throw new \InvalidArgumentException(
'A namespaced tag must have a leading backslash as it must be fully qualified'
);
}
$this->tagHandlerMappings[$tagName] = $handler;
}
/**
* Extracts all components for a tag.
*
* @param string $tagLine
*
* @return string[]
*/
private function extractTagParts($tagLine)
{
$matches = array();
if (! preg_match('/^@(' . self::REGEX_TAGNAME . ')(?:\s*([^\s].*)|$)?/us', $tagLine, $matches)) {
throw new \InvalidArgumentException(
'The tag "' . $tagLine . '" does not seem to be wellformed, please check it for errors'
);
}
if (count($matches) < 3) {
$matches[] = '';
}
return array_slice($matches, 1);
}
/**
* Creates a new tag object with the given name and body or returns null if the tag name was recognized but the
* body was invalid.
*
* @param string $body
* @param string $name
* @param TypeContext $context
*
* @return Tag|null
*/
private function createTag($body, $name, TypeContext $context)
{
$handlerClassName = $this->findHandlerClassName($name, $context);
$arguments = $this->getArgumentsForParametersFromWiring(
$this->fetchParametersForHandlerFactoryMethod($handlerClassName),
$this->getServiceLocatorWithDynamicParameters($context, $name, $body)
)
;
return call_user_func_array([$handlerClassName, 'create'], $arguments);
}
/**
* Determines the Fully Qualified Class Name of the Factory or Tag (containing a Factory Method `create`).
*
* @param string $tagName
* @param TypeContext $context
*
* @return string
*/
private function findHandlerClassName($tagName, TypeContext $context)
{
$handlerClassName = Generic::class;
if (isset($this->tagHandlerMappings[$tagName])) {
$handlerClassName = $this->tagHandlerMappings[$tagName];
} elseif ($this->isAnnotation($tagName)) {
// TODO: Annotation support is planned for a later stage and as such is disabled for now
// $tagName = (string)$this->fqsenResolver->resolve($tagName, $context);
// if (isset($this->annotationMappings[$tagName])) {
// $handlerClassName = $this->annotationMappings[$tagName];
// }
}
return $handlerClassName;
}
/**
* Retrieves the arguments that need to be passed to the Factory Method with the given Parameters.
*
* @param \ReflectionParameter[] $parameters
* @param mixed[] $locator
*
* @return mixed[] A series of values that can be passed to the Factory Method of the tag whose parameters
* is provided with this method.
*/
private function getArgumentsForParametersFromWiring($parameters, $locator)
{
$arguments = [];
foreach ($parameters as $index => $parameter) {
$typeHint = $parameter->getClass() ? $parameter->getClass()->getName() : null;
if (isset($locator[$typeHint])) {
$arguments[] = $locator[$typeHint];
continue;
}
$parameterName = $parameter->getName();
if (isset($locator[$parameterName])) {
$arguments[] = $locator[$parameterName];
continue;
}
$arguments[] = null;
}
return $arguments;
}
/**
* Retrieves a series of ReflectionParameter objects for the static 'create' method of the given
* tag handler class name.
*
* @param string $handlerClassName
*
* @return \ReflectionParameter[]
*/
private function fetchParametersForHandlerFactoryMethod($handlerClassName)
{
if (! isset($this->tagHandlerParameterCache[$handlerClassName])) {
$methodReflection = new \ReflectionMethod($handlerClassName, 'create');
$this->tagHandlerParameterCache[$handlerClassName] = $methodReflection->getParameters();
}
return $this->tagHandlerParameterCache[$handlerClassName];
}
/**
* Returns a copy of this class' Service Locator with added dynamic parameters, such as the tag's name, body and
* Context.
*
* @param TypeContext $context The Context (namespace and aliasses) that may be passed and is used to resolve FQSENs.
* @param string $tagName The name of the tag that may be passed onto the factory method of the Tag class.
* @param string $tagBody The body of the tag that may be passed onto the factory method of the Tag class.
*
* @return mixed[]
*/
private function getServiceLocatorWithDynamicParameters(TypeContext $context, $tagName, $tagBody)
{
$locator = array_merge(
$this->serviceLocator,
[
'name' => $tagName,
'body' => $tagBody,
TypeContext::class => $context
]
);
return $locator;
}
/**
* Returns whether the given tag belongs to an annotation.
*
* @param string $tagContent
*
* @todo this method should be populated once we implement Annotation notation support.
*
* @return bool
*/
private function isAnnotation($tagContent)
{
// 1. Contains a namespace separator
// 2. Contains parenthesis
// 3. Is present in a list of known annotations (make the algorithm smart by first checking is the last part
// of the annotation class name matches the found tag name
return false;
}
}
<?php
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\DocBlock;
use phpDocumentor\Reflection\DocBlock\Tags\Formatter;
interface Tag
{
public function getName();
public static function create($body);
public function render(Formatter $formatter = null);
public function __toString();
}
<?php
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\DocBlock;
use phpDocumentor\Reflection\Types\Context as TypeContext;
interface TagFactory
{
/**
* Adds a parameter to the service locator that can be injected in a tag's factory method.
*
* When calling a tag's "create" method we always check the signature for dependencies to inject. One way is to
* typehint a parameter in the signature so that we can use that interface or class name to inject a dependency
* (see {@see addService()} for more information on that).
*
* Another way is to check the name of the argument against the names in the Service Locator. With this method
* you can add a variable that will be inserted when a tag's create method is not typehinted and has a matching
* name.
*
* Be aware that there are two reserved names:
*
* - name, representing the name of the tag.
* - body, representing the complete body of the tag.
*
* These parameters are injected at the last moment and will override any existing parameter with those names.
*
* @param string $name
* @param mixed $value
*
* @return void
*/
public function addParameter($name, $value);
/**
* Registers a service with the Service Locator using the FQCN of the class or the alias, if provided.
*
* When calling a tag's "create" method we always check the signature for dependencies to inject. If a parameter
* has a typehint then the ServiceLocator is queried to see if a Service is registered for that typehint.
*
* Because interfaces are regularly used as type-hints this method provides an alias parameter; if the FQCN of the
* interface is passed as alias then every time that interface is requested the provided service will be returned.
*
* @param object $service
* @param string $alias
*
* @return void
*/
public function addService($service);
/**
* Factory method responsible for instantiating the correct sub type.
*
* @param string $tagLine The text for this tag, including description.
* @param TypeContext $context
*
* @throws \InvalidArgumentException if an invalid tag line was presented.
*
* @return Tag A new tag object.
*/
public function create($tagLine, TypeContext $context = null);
/**
* Registers a handler for tags.
*
* If you want to use your own tags then you can use this method to instruct the TagFactory to register the name
* of a tag with the FQCN of a 'Tag Handler'. The Tag handler should implement the {@see Tag} interface (and thus
* the create method).
*
* @param string $tagName Name of tag to register a handler for. When registering a namespaced tag, the full
* name, along with a prefixing slash MUST be provided.
* @param string $handler FQCN of handler.
*
* @throws \InvalidArgumentException if the tag name is not a string
* @throws \InvalidArgumentException if the tag name is namespaced (contains backslashes) but does not start with
* a backslash
* @throws \InvalidArgumentException if the handler is not a string
* @throws \InvalidArgumentException if the handler is not an existing class
* @throws \InvalidArgumentException if the handler does not implement the {@see Tag} interface
*
* @return void
*/
public function registerTagHandler($tagName, $handler);
}
<?php
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\DocBlock\Tags;
use Webmozart\Assert\Assert;
/**
* Reflection class for an {@}author tag in a Docblock.
*/
final class Author extends BaseTag implements Factory\StaticMethod
{
/** @var string register that this is the author tag. */
protected $name = 'author';
/** @var string The name of the author */
private $authorName = '';
/** @var string The email of the author */
private $authorEmail = '';
/**
* Initializes this tag with the author name and e-mail.
*
* @param string $authorName
* @param string $authorEmail
*/
public function __construct($authorName, $authorEmail)
{
Assert::string($authorName);
Assert::string($authorEmail);
if ($authorEmail && !filter_var($authorEmail, FILTER_VALIDATE_EMAIL)) {
throw new \InvalidArgumentException('The author tag does not have a valid e-mail address');
}
$this->authorName = $authorName;
$this->authorEmail = $authorEmail;
}
/**
* Gets the author's name.
*
* @return string The author's name.
*/
public function getAuthorName()
{
return $this->authorName;
}
/**
* Returns the author's email.
*
* @return string The author's email.
*/
public function getEmail()
{
return $this->authorEmail;
}
/**
* Returns this tag in string form.
*
* @return string
*/
public function __toString()
{
return $this->authorName . '<' . $this->authorEmail . '>';
}
/**
* Attempts to create a new Author object based on †he tag body.
*
* @param string $body
*
* @return static
*/
public static function create($body)
{
Assert::string($body);
$splitTagContent = preg_match('/^([^\<]*)(?:\<([^\>]*)\>)?$/u', $body, $matches);
if (!$splitTagContent) {
return null;
}
$authorName = trim($matches[1]);
$email = isset($matches[2]) ? trim($matches[2]) : '';
return new static($authorName, $email);
}
}
<?php
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\DocBlock\Tags;
use phpDocumentor\Reflection\DocBlock;
use phpDocumentor\Reflection\DocBlock\Description;
/**
* Parses a tag definition for a DocBlock.
*/
abstract class BaseTag implements DocBlock\Tag
{
/** @var string Name of the tag */
protected $name = '';
/** @var Description|null Description of the tag. */
protected $description;
/**
* Gets the name of this tag.
*
* @return string The name of this tag.
*/
public function getName()
{
return $this->name;
}
public function getDescription()
{
return $this->description;
}
public function render(Formatter $formatter = null)
{
if ($formatter === null) {
$formatter = new Formatter\PassthroughFormatter();
}
return $formatter->format($this);
}
}
<?php
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\DocBlock\Tags;
use phpDocumentor\Reflection\DocBlock\DescriptionFactory;
use phpDocumentor\Reflection\Fqsen;
use phpDocumentor\Reflection\DocBlock\Description;
use phpDocumentor\Reflection\Types\Context as TypeContext;
use phpDocumentor\Reflection\FqsenResolver;
use Webmozart\Assert\Assert;
/**
* Reflection class for a @covers tag in a Docblock.
*/
final class Covers extends BaseTag implements Factory\StaticMethod
{
protected $name = 'covers';
/** @var Fqsen */
private $refers = null;
/**
* Initializes this tag.
*
* @param Fqsen $refers
* @param Description $description
*/
public function __construct(Fqsen $refers, Description $description = null)
{
$this->refers = $refers;
$this->description = $description;
}
/**
* {@inheritdoc}
*/
public static function create(
$body,
DescriptionFactory $descriptionFactory = null,
FqsenResolver $resolver = null,
TypeContext $context = null
)
{
Assert::string($body);
Assert::notEmpty($body);
$parts = preg_split('/\s+/Su', $body, 2);
return new static(
$resolver->resolve($parts[0], $context),
$descriptionFactory->create(isset($parts[1]) ? $parts[1] : '', $context)
);
}
/**
* Returns the structural element this tag refers to.
*
* @return Fqsen
*/
public function getReference()
{
return $this->refers;
}
/**
* Returns a string representation of this tag.
*
* @return string
*/
public function __toString()
{
return $this->refers . ($this->description ? ' ' . $this->description->render() : '');
}
}
<?php
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\DocBlock\Tags;
use phpDocumentor\Reflection\Types\Context as TypeContext;
use phpDocumentor\Reflection\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\DescriptionFactory;
use Webmozart\Assert\Assert;
/**
* Reflection class for a {@}deprecated tag in a Docblock.
*/
final class Deprecated extends BaseTag implements Factory\StaticMethod
{
protected $name = 'deprecated';
/**
* PCRE regular expression matching a version vector.
* Assumes the "x" modifier.
*/
const REGEX_VECTOR = '(?:
# Normal release vectors.
\d\S*
|
# VCS version vectors. Per PHPCS, they are expected to
# follow the form of the VCS name, followed by ":", followed
# by the version vector itself.
# By convention, popular VCSes like CVS, SVN and GIT use "$"
# around the actual version vector.
[^\s\:]+\:\s*\$[^\$]+\$
)';
/** @var string The version vector. */
private $version = '';
public function __construct($version = null, Description $description = null)
{
Assert::nullOrStringNotEmpty($version);
$this->version = $version;
$this->description = $description;
}
/**
* @return static
*/
public static function create($body, DescriptionFactory $descriptionFactory = null, TypeContext $context = null)
{
Assert::nullOrString($body);
if (empty($body)) {
return new static();
}
$matches = [];
if (!preg_match('/^(' . self::REGEX_VECTOR . ')\s*(.+)?$/sux', $body, $matches)) {
return new static(
null,
null !== $descriptionFactory ? $descriptionFactory->create($body, $context) : null
);
}
return new static(
$matches[1],
$descriptionFactory->create(isset($matches[2]) ? $matches[2] : '', $context)
);
}
/**
* Gets the version section of the tag.
*
* @return string
*/
public function getVersion()
{
return $this->version;
}
/**
* Returns a string representation for this tag.
*
* @return string
*/
public function __toString()
{
return $this->version . ($this->description ? ' ' . $this->description->render() : '');
}
}
<?php
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\DocBlock\Tags;
use phpDocumentor\Reflection\DocBlock\Tag;
/**
* Reflection class for a {@}example tag in a Docblock.
*/
final class Example extends BaseTag
{
/**
* @var string Path to a file to use as an example. May also be an absolute URI.
*/
private $filePath = '';
/**
* @var bool Whether the file path component represents an URI. This determines how the file portion
* appears at {@link getContent()}.
*/
private $isURI = false;
/**
* {@inheritdoc}
*/
public function getContent()
{
if (null === $this->description) {
$filePath = '"' . $this->filePath . '"';
if ($this->isURI) {
$filePath = $this->isUriRelative($this->filePath)
? str_replace('%2F', '/', rawurlencode($this->filePath))
:$this->filePath;
}
$this->description = $filePath . ' ' . parent::getContent();
}
return $this->description;
}
/**
* {@inheritdoc}
*/
public static function create($body)
{
// File component: File path in quotes or File URI / Source information
if (! preg_match('/^(?:\"([^\"]+)\"|(\S+))(?:\s+(.*))?$/sux', $body, $matches)) {
return null;
}
$filePath = null;
$fileUri = null;
if ('' !== $matches[1]) {
$filePath = $matches[1];
} else {
$fileUri = $matches[2];
}
$startingLine = 1;
$lineCount = null;
$description = null;
// Starting line / Number of lines / Description
if (preg_match('/^([1-9]\d*)\s*(?:((?1))\s+)?(.*)$/sux', $matches[3], $matches)) {
$startingLine = (int)$matches[1];
if (isset($matches[2]) && $matches[2] !== '') {
$lineCount = (int)$matches[2];
}
$description = $matches[3];
}
return new static($filePath, $fileUri, $startingLine, $lineCount, $description);
}
/**
* Returns the file path.
*
* @return string Path to a file to use as an example.
* May also be an absolute URI.
*/
public function getFilePath()
{
return $this->filePath;
}
/**
* Sets the file path.
*
* @param string $filePath The new file path to use for the example.
*
* @return $this
*/
public function setFilePath($filePath)
{
$this->isURI = false;
$this->filePath = trim($filePath);
$this->description = null;
return $this;
}
/**
* Sets the file path as an URI.
*
* This function is equivalent to {@link setFilePath()}, except that it
* converts an URI to a file path before that.
*
* There is no getFileURI(), as {@link getFilePath()} is compatible.
*
* @param string $uri The new file URI to use as an example.
*
* @return $this
*/
public function setFileURI($uri)
{
$this->isURI = true;
$this->description = null;
$this->filePath = $this->isUriRelative($uri)
? rawurldecode(str_replace(array('/', '\\'), '%2F', $uri))
: $this->filePath = $uri;
return $this;
}
/**
* Returns a string representation for this tag.
*
* @return string
*/
public function __toString()
{
return $this->filePath . ($this->description ? ' ' . $this->description->render() : '');
}
/**
* Returns true if the provided URI is relative or contains a complete scheme (and thus is absolute).
*
* @param string $uri
*
* @return bool
*/
private function isUriRelative($uri)
{
return false === strpos($uri, ':');
}
}
<?php
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\DocBlock\Tags\Factory;
interface StaticMethod
{
public static function create($body);
}
<?php
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\DocBlock\Tags\Factory;
interface Strategy
{
public function create($body);
}
<?php
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\DocBlock\Tags\Formatter;
use phpDocumentor\Reflection\DocBlock\Tag;
use phpDocumentor\Reflection\DocBlock\Tags\Formatter;
class PassthroughFormatter implements Formatter
{
/**
* Formats the given tag to return a simple plain text version.
*
* @param Tag $tag
*
* @return string
*/
public function format(Tag $tag)
{
return '@' . $tag->getName() . ' ' . (string)$tag;
}
}
<?php
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\DocBlock\Tags;
use phpDocumentor\Reflection\DocBlock\Tag;
interface Formatter
{
/**
* Formats a tag into a string representation according to a specific format, such as Markdown.
*
* @param Tag $tag
*
* @return string
*/
public function format(Tag $tag);
}
<?php
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\DocBlock\Tags;
use phpDocumentor\Reflection\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\DescriptionFactory;
use phpDocumentor\Reflection\DocBlock\StandardTagFactory;
use phpDocumentor\Reflection\Types\Context as TypeContext;
use Webmozart\Assert\Assert;
/**
* Parses a tag definition for a DocBlock.
*/
class Generic extends BaseTag implements Factory\StaticMethod
{
/**
* Parses a tag and populates the member variables.
*
* @param string $name Name of the tag.
* @param Description $description The contents of the given tag.
*/
public function __construct($name, Description $description = null)
{
$this->validateTagName($name);
$this->name = $name;
$this->description = $description;
}
/**
* Creates a new tag that represents any unknown tag type.
*
* @param string $body
* @param string $name
* @param DescriptionFactory $descriptionFactory
* @param TypeContext $context
*
* @return static
*/
public static function create(
$body,
$name = '',
DescriptionFactory $descriptionFactory = null,
TypeContext $context = null
) {
Assert::string($body);
Assert::stringNotEmpty($name);
Assert::notNull($descriptionFactory);
$description = $descriptionFactory && $body ? $descriptionFactory->create($body, $context) : null;
return new static($name, $description);
}
/**
* Returns the tag as a serialized string
*
* @return string
*/
public function __toString()
{
return ($this->description ? $this->description->render() : '');
}
/**
* Validates if the tag name matches the expected format, otherwise throws an exception.
*
* @param string $name
*
* @return void
*/
private function validateTagName($name)
{
if (! preg_match('/^' . StandardTagFactory::REGEX_TAGNAME . '$/u', $name)) {
throw new \InvalidArgumentException(
'The tag name "' . $name . '" is not wellformed. Tags may only consist of letters, underscores, '
. 'hyphens and backslashes.'
);
}
}
}
<?php
/**
* phpDocumentor
*
* PHP Version 5.3
*
* @author Ben Selby <benmatselby@gmail.com>
* @copyright 2010-2011 Mike van Riel / Naenius (http://www.naenius.com)
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\DocBlock\Tags;
use phpDocumentor\Reflection\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\DescriptionFactory;
use phpDocumentor\Reflection\Types\Context as TypeContext;
use Webmozart\Assert\Assert;
/**
* Reflection class for a @link tag in a Docblock.
*/
final class Link extends BaseTag implements Factory\StaticMethod
{
protected $name = 'link';
/** @var string */
private $link = '';
/**
* Initializes a link to a URL.
*
* @param string $link
* @param Description $description
*/
public function __construct($link, Description $description = null)
{
Assert::string($link);
$this->link = $link;
$this->description = $description;
}
/**
* {@inheritdoc}
*/
public static function create($body, DescriptionFactory $descriptionFactory = null, TypeContext $context = null)
{
Assert::string($body);
Assert::notNull($descriptionFactory);
$parts = preg_split('/\s+/Su', $body, 2);
$description = isset($parts[1]) ? $descriptionFactory->create($parts[1], $context) : null;
return new static($parts[0], $description);
}
/**
* Gets the link
*
* @return string
*/
public function getLink()
{
return $this->link;
}
/**
* Returns a string representation for this tag.
*
* @return string
*/
public function __toString()
{
return $this->link . ($this->description ? ' ' . $this->description->render() : '');
}
}
<?php
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\DocBlock\Tags;
use phpDocumentor\Reflection\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\DescriptionFactory;
use phpDocumentor\Reflection\Type;
use phpDocumentor\Reflection\TypeResolver;
use phpDocumentor\Reflection\Types\Context as TypeContext;
use phpDocumentor\Reflection\Types\Void_;
use Webmozart\Assert\Assert;
/**
* Reflection class for an {@}method in a Docblock.
*/
final class Method extends BaseTag implements Factory\StaticMethod
{
protected $name = 'method';
/** @var string */
private $methodName = '';
/** @var string[] */
private $arguments = [];
/** @var bool */
private $isStatic = false;
/** @var Type */
private $returnType;
public function __construct(
$methodName,
array $arguments = [],
Type $returnType = null,
$static = false,
Description $description = null
) {
Assert::stringNotEmpty($methodName);
Assert::boolean($static);
if ($returnType === null) {
$returnType = new Void_();
}
$this->methodName = $methodName;
$this->arguments = $this->filterArguments($arguments);
$this->returnType = $returnType;
$this->isStatic = $static;
$this->description = $description;
}
/**
* {@inheritdoc}
*/
public static function create(
$body,
TypeResolver $typeResolver = null,
DescriptionFactory $descriptionFactory = null,
TypeContext $context = null
) {
Assert::stringNotEmpty($body);
Assert::allNotNull([ $typeResolver, $descriptionFactory ]);
// 1. none or more whitespace
// 2. optionally the keyword "static" followed by whitespace
// 3. optionally a word with underscores followed by whitespace : as
// type for the return value
// 4. then optionally a word with underscores followed by () and
// whitespace : as method name as used by phpDocumentor
// 5. then a word with underscores, followed by ( and any character
// until a ) and whitespace : as method name with signature
// 6. any remaining text : as description
if (!preg_match(
'/^
# Static keyword
# Declares a static method ONLY if type is also present
(?:
(static)
\s+
)?
# Return type
(?:
(
(?:[\w\|_\\\\]+)
# array notation
(?:\[\])*
)?
\s+
)?
# Legacy method name (not captured)
(?:
[\w_]+\(\)\s+
)?
# Method name
([\w\|_\\\\]+)
# Arguments
(?:
\(([^\)]*)\)
)?
\s*
# Description
(.*)
$/sux',
$body,
$matches
)) {
return null;
}
list(, $static, $returnType, $methodName, $arguments, $description) = $matches;
$static = $static === 'static';
$returnType = $typeResolver->resolve($returnType, $context);
$description = $descriptionFactory->create($description, $context);
if ('' !== $arguments) {
$arguments = explode(',', $arguments);
foreach($arguments as &$argument) {
$argument = explode(' ', self::stripRestArg(trim($argument)), 2);
if ($argument[0][0] === '$') {
$argumentName = substr($argument[0], 1);
$argumentType = new Void_();
} else {
$argumentType = $typeResolver->resolve($argument[0], $context);
$argumentName = '';
if (isset($argument[1])) {
$argument[1] = self::stripRestArg($argument[1]);
$argumentName = substr($argument[1], 1);
}
}
$argument = [ 'name' => $argumentName, 'type' => $argumentType];
}
} else {
$arguments = [];
}
return new static($methodName, $arguments, $returnType, $static, $description);
}
/**
* Retrieves the method name.
*
* @return string
*/
public function getMethodName()
{
return $this->methodName;
}
/**
* @return string[]
*/
public function getArguments()
{
return $this->arguments;
}
/**
* Checks whether the method tag describes a static method or not.
*
* @return bool TRUE if the method declaration is for a static method, FALSE otherwise.
*/
public function isStatic()
{
return $this->isStatic;
}
/**
* @return Type
*/
public function getReturnType()
{
return $this->returnType;
}
public function __toString()
{
$arguments = [];
foreach ($this->arguments as $argument) {
$arguments[] = $argument['type'] . ' $' . $argument['name'];
}
return ($this->isStatic() ? 'static ' : '')
. (string)$this->returnType . ' '
. $this->methodName
. '(' . implode(', ', $arguments) . ')'
. ($this->description ? ' ' . $this->description->render() : '');
}
private function filterArguments($arguments)
{
foreach ($arguments as &$argument) {
if (is_string($argument)) {
$argument = [ 'name' => $argument ];
}
if (! isset($argument['type'])) {
$argument['type'] = new Void_();
}
$keys = array_keys($argument);
if ($keys !== [ 'name', 'type' ]) {
throw new \InvalidArgumentException(
'Arguments can only have the "name" and "type" fields, found: ' . var_export($keys, true)
);
}
}
return $arguments;
}
private static function stripRestArg($argument)
{
if (strpos($argument, '...') === 0) {
$argument = trim(substr($argument, 3));
}
return $argument;
}
}
<?php
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\DocBlock\Tags;
use phpDocumentor\Reflection\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\DescriptionFactory;
use phpDocumentor\Reflection\Type;
use phpDocumentor\Reflection\TypeResolver;
use phpDocumentor\Reflection\Types\Context as TypeContext;
use Webmozart\Assert\Assert;
/**
* Reflection class for the {@}param tag in a Docblock.
*/
final class Param extends BaseTag implements Factory\StaticMethod
{
/** @var string */
protected $name = 'param';
/** @var Type */
private $type;
/** @var string */
private $variableName = '';
/** @var bool determines whether this is a variadic argument */
private $isVariadic = false;
/**
* @param string $variableName
* @param Type $type
* @param bool $isVariadic
* @param Description $description
*/
public function __construct($variableName, Type $type = null, $isVariadic = false, Description $description = null)
{
Assert::string($variableName);
Assert::boolean($isVariadic);
$this->variableName = $variableName;
$this->type = $type;
$this->isVariadic = $isVariadic;
$this->description = $description;
}
/**
* {@inheritdoc}
*/
public static function create(
$body,
TypeResolver $typeResolver = null,
DescriptionFactory $descriptionFactory = null,
TypeContext $context = null
) {
Assert::stringNotEmpty($body);
Assert::allNotNull([$typeResolver, $descriptionFactory]);
$parts = preg_split('/(\s+)/Su', $body, 3, PREG_SPLIT_DELIM_CAPTURE);
$type = null;
$variableName = '';
$isVariadic = false;
// if the first item that is encountered is not a variable; it is a type
if (isset($parts[0]) && (strlen($parts[0]) > 0) && ($parts[0][0] !== '$')) {
$type = $typeResolver->resolve(array_shift($parts), $context);
array_shift($parts);
}
// if the next item starts with a $ or ...$ it must be the variable name
if (isset($parts[0]) && (strlen($parts[0]) > 0) && ($parts[0][0] == '$' || substr($parts[0], 0, 4) === '...$')) {
$variableName = array_shift($parts);
array_shift($parts);
if (substr($variableName, 0, 3) === '...') {
$isVariadic = true;
$variableName = substr($variableName, 3);
}
if (substr($variableName, 0, 1) === '$') {
$variableName = substr($variableName, 1);
}
}
$description = $descriptionFactory->create(implode('', $parts), $context);
return new static($variableName, $type, $isVariadic, $description);
}
/**
* Returns the variable's name.
*
* @return string
*/
public function getVariableName()
{
return $this->variableName;
}
/**
* Returns the variable's type or null if unknown.
*
* @return Type|null
*/
public function getType()
{
return $this->type;
}
/**
* Returns whether this tag is variadic.
*
* @return boolean
*/
public function isVariadic()
{
return $this->isVariadic;
}
/**
* Returns a string representation for this tag.
*
* @return string
*/
public function __toString()
{
return ($this->type ? $this->type . ' ' : '')
. ($this->isVariadic() ? '...' : '')
. '$' . $this->variableName
. ($this->description ? ' ' . $this->description : '');
}
}
<?php
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\DocBlock\Tags;
use phpDocumentor\Reflection\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\DescriptionFactory;
use phpDocumentor\Reflection\Type;
use phpDocumentor\Reflection\TypeResolver;
use phpDocumentor\Reflection\Types\Context as TypeContext;
use Webmozart\Assert\Assert;
/**
* Reflection class for a {@}property tag in a Docblock.
*/
class Property extends BaseTag implements Factory\StaticMethod
{
/** @var string */
protected $name = 'property';
/** @var Type */
private $type;
/** @var string */
protected $variableName = '';
/**
* @param string $variableName
* @param Type $type
* @param Description $description
*/
public function __construct($variableName, Type $type = null, Description $description = null)
{
Assert::string($variableName);
$this->variableName = $variableName;
$this->type = $type;
$this->description = $description;
}
/**
* {@inheritdoc}
*/
public static function create(
$body,
TypeResolver $typeResolver = null,
DescriptionFactory $descriptionFactory = null,
TypeContext $context = null
) {
Assert::stringNotEmpty($body);
Assert::allNotNull([$typeResolver, $descriptionFactory]);
$parts = preg_split('/(\s+)/Su', $body, 3, PREG_SPLIT_DELIM_CAPTURE);
$type = null;
$variableName = '';
// if the first item that is encountered is not a variable; it is a type
if (isset($parts[0]) && (strlen($parts[0]) > 0) && ($parts[0][0] !== '$')) {
$type = $typeResolver->resolve(array_shift($parts), $context);
array_shift($parts);
}
// if the next item starts with a $ or ...$ it must be the variable name
if (isset($parts[0]) && (strlen($parts[0]) > 0) && ($parts[0][0] == '$')) {
$variableName = array_shift($parts);
array_shift($parts);
if (substr($variableName, 0, 1) === '$') {
$variableName = substr($variableName, 1);
}
}
$description = $descriptionFactory->create(implode('', $parts), $context);
return new static($variableName, $type, $description);
}
/**
* Returns the variable's name.
*
* @return string
*/
public function getVariableName()
{
return $this->variableName;
}
/**
* Returns the variable's type or null if unknown.
*
* @return Type|null
*/
public function getType()
{
return $this->type;
}
/**
* Returns a string representation for this tag.
*
* @return string
*/
public function __toString()
{
return ($this->type ? $this->type . ' ' : '')
. '$' . $this->variableName
. ($this->description ? ' ' . $this->description : '');
}
}
<?php
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\DocBlock\Tags;
use phpDocumentor\Reflection\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\DescriptionFactory;
use phpDocumentor\Reflection\Type;
use phpDocumentor\Reflection\TypeResolver;
use phpDocumentor\Reflection\Types\Context as TypeContext;
use Webmozart\Assert\Assert;
/**
* Reflection class for a {@}property-read tag in a Docblock.
*/
class PropertyRead extends BaseTag implements Factory\StaticMethod
{
/** @var string */
protected $name = 'property-read';
/** @var Type */
private $type;
/** @var string */
protected $variableName = '';
/**
* @param string $variableName
* @param Type $type
* @param Description $description
*/
public function __construct($variableName, Type $type = null, Description $description = null)
{
Assert::string($variableName);
$this->variableName = $variableName;
$this->type = $type;
$this->description = $description;
}
/**
* {@inheritdoc}
*/
public static function create(
$body,
TypeResolver $typeResolver = null,
DescriptionFactory $descriptionFactory = null,
TypeContext $context = null
) {
Assert::stringNotEmpty($body);
Assert::allNotNull([$typeResolver, $descriptionFactory]);
$parts = preg_split('/(\s+)/Su', $body, 3, PREG_SPLIT_DELIM_CAPTURE);
$type = null;
$variableName = '';
// if the first item that is encountered is not a variable; it is a type
if (isset($parts[0]) && (strlen($parts[0]) > 0) && ($parts[0][0] !== '$')) {
$type = $typeResolver->resolve(array_shift($parts), $context);
array_shift($parts);
}
// if the next item starts with a $ or ...$ it must be the variable name
if (isset($parts[0]) && (strlen($parts[0]) > 0) && ($parts[0][0] == '$')) {
$variableName = array_shift($parts);
array_shift($parts);
if (substr($variableName, 0, 1) === '$') {
$variableName = substr($variableName, 1);
}
}
$description = $descriptionFactory->create(implode('', $parts), $context);
return new static($variableName, $type, $description);
}
/**
* Returns the variable's name.
*
* @return string
*/
public function getVariableName()
{
return $this->variableName;
}
/**
* Returns the variable's type or null if unknown.
*
* @return Type|null
*/
public function getType()
{
return $this->type;
}
/**
* Returns a string representation for this tag.
*
* @return string
*/
public function __toString()
{
return ($this->type ? $this->type . ' ' : '')
. '$' . $this->variableName
. ($this->description ? ' ' . $this->description : '');
}
}
<?php
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\DocBlock\Tags;
use phpDocumentor\Reflection\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\DescriptionFactory;
use phpDocumentor\Reflection\Type;
use phpDocumentor\Reflection\TypeResolver;
use phpDocumentor\Reflection\Types\Context as TypeContext;
use Webmozart\Assert\Assert;
/**
* Reflection class for a {@}property-write tag in a Docblock.
*/
class PropertyWrite extends BaseTag implements Factory\StaticMethod
{
/** @var string */
protected $name = 'property-write';
/** @var Type */
private $type;
/** @var string */
protected $variableName = '';
/**
* @param string $variableName
* @param Type $type
* @param Description $description
*/
public function __construct($variableName, Type $type = null, Description $description = null)
{
Assert::string($variableName);
$this->variableName = $variableName;
$this->type = $type;
$this->description = $description;
}
/**
* {@inheritdoc}
*/
public static function create(
$body,
TypeResolver $typeResolver = null,
DescriptionFactory $descriptionFactory = null,
TypeContext $context = null
) {
Assert::stringNotEmpty($body);
Assert::allNotNull([$typeResolver, $descriptionFactory]);
$parts = preg_split('/(\s+)/Su', $body, 3, PREG_SPLIT_DELIM_CAPTURE);
$type = null;
$variableName = '';
// if the first item that is encountered is not a variable; it is a type
if (isset($parts[0]) && (strlen($parts[0]) > 0) && ($parts[0][0] !== '$')) {
$type = $typeResolver->resolve(array_shift($parts), $context);
array_shift($parts);
}
// if the next item starts with a $ or ...$ it must be the variable name
if (isset($parts[0]) && (strlen($parts[0]) > 0) && ($parts[0][0] == '$')) {
$variableName = array_shift($parts);
array_shift($parts);
if (substr($variableName, 0, 1) === '$') {
$variableName = substr($variableName, 1);
}
}
$description = $descriptionFactory->create(implode('', $parts), $context);
return new static($variableName, $type, $description);
}
/**
* Returns the variable's name.
*
* @return string
*/
public function getVariableName()
{
return $this->variableName;
}
/**
* Returns the variable's type or null if unknown.
*
* @return Type|null
*/
public function getType()
{
return $this->type;
}
/**
* Returns a string representation for this tag.
*
* @return string
*/
public function __toString()
{
return ($this->type ? $this->type . ' ' : '')
. '$' . $this->variableName
. ($this->description ? ' ' . $this->description : '');
}
}
<?php
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\DocBlock\Tags;
use phpDocumentor\Reflection\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\DescriptionFactory;
use phpDocumentor\Reflection\Type;
use phpDocumentor\Reflection\TypeResolver;
use phpDocumentor\Reflection\Types\Context as TypeContext;
use Webmozart\Assert\Assert;
/**
* Reflection class for a {@}return tag in a Docblock.
*/
final class Return_ extends BaseTag implements Factory\StaticMethod
{
protected $name = 'return';
/** @var Type */
private $type;
public function __construct(Type $type, Description $description = null)
{
$this->type = $type;
$this->description = $description;
}
/**
* {@inheritdoc}
*/
public static function create(
$body,
TypeResolver $typeResolver = null,
DescriptionFactory $descriptionFactory = null,
TypeContext $context = null
)
{
Assert::string($body);
Assert::allNotNull([$typeResolver, $descriptionFactory]);
$parts = preg_split('/\s+/Su', $body, 2);
$type = $typeResolver->resolve(isset($parts[0]) ? $parts[0] : '', $context);
$description = $descriptionFactory->create(isset($parts[1]) ? $parts[1] : '', $context);
return new static($type, $description);
}
/**
* Returns the type section of the variable.
*
* @return Type
*/
public function getType()
{
return $this->type;
}
public function __toString()
{
return $this->type . ' ' . $this->description;
}
}
<?php
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\DocBlock\Tags;
use phpDocumentor\Reflection\DocBlock\DescriptionFactory;
use phpDocumentor\Reflection\Fqsen;
use phpDocumentor\Reflection\FqsenResolver;
use phpDocumentor\Reflection\Types\Context as TypeContext;
use phpDocumentor\Reflection\DocBlock\Description;
use Webmozart\Assert\Assert;
/**
* Reflection class for an {@}see tag in a Docblock.
*/
class See extends BaseTag implements Factory\StaticMethod
{
protected $name = 'see';
/** @var Fqsen */
protected $refers = null;
/**
* Initializes this tag.
*
* @param Fqsen $refers
* @param Description $description
*/
public function __construct(Fqsen $refers, Description $description = null)
{
$this->refers = $refers;
$this->description = $description;
}
/**
* {@inheritdoc}
*/
public static function create(
$body,
FqsenResolver $resolver = null,
DescriptionFactory $descriptionFactory = null,
TypeContext $context = null
) {
Assert::string($body);
Assert::allNotNull([$resolver, $descriptionFactory]);
$parts = preg_split('/\s+/Su', $body, 2);
$description = isset($parts[1]) ? $descriptionFactory->create($parts[1], $context) : null;
return new static($resolver->resolve($parts[0], $context), $description);
}
/**
* Returns the structural element this tag refers to.
*
* @return Fqsen
*/
public function getReference()
{
return $this->refers;
}
/**
* Returns a string representation of this tag.
*
* @return string
*/
public function __toString()
{
return $this->refers . ($this->description ? ' ' . $this->description->render() : '');
}
}
<?php
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\DocBlock\Tags;
use phpDocumentor\Reflection\Types\Context as TypeContext;
use phpDocumentor\Reflection\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\DescriptionFactory;
use Webmozart\Assert\Assert;
/**
* Reflection class for a {@}since tag in a Docblock.
*/
final class Since extends BaseTag implements Factory\StaticMethod
{
protected $name = 'since';
/**
* PCRE regular expression matching a version vector.
* Assumes the "x" modifier.
*/
const REGEX_VECTOR = '(?:
# Normal release vectors.
\d\S*
|
# VCS version vectors. Per PHPCS, they are expected to
# follow the form of the VCS name, followed by ":", followed
# by the version vector itself.
# By convention, popular VCSes like CVS, SVN and GIT use "$"
# around the actual version vector.
[^\s\:]+\:\s*\$[^\$]+\$
)';
/** @var string The version vector. */
private $version = '';
public function __construct($version = null, Description $description = null)
{
Assert::nullOrStringNotEmpty($version);
$this->version = $version;
$this->description = $description;
}
/**
* @return static
*/
public static function create($body, DescriptionFactory $descriptionFactory = null, TypeContext $context = null)
{
Assert::nullOrString($body);
if (empty($body)) {
return new static();
}
$matches = [];
if (! preg_match('/^(' . self::REGEX_VECTOR . ')\s*(.+)?$/sux', $body, $matches)) {
return null;
}
return new static(
$matches[1],
$descriptionFactory->create(isset($matches[2]) ? $matches[2] : '', $context)
);
}
/**
* Gets the version section of the tag.
*
* @return string
*/
public function getVersion()
{
return $this->version;
}
/**
* Returns a string representation for this tag.
*
* @return string
*/
public function __toString()
{
return $this->version . ($this->description ? ' ' . $this->description->render() : '');
}
}
<?php
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\DocBlock\Tags;
use phpDocumentor\Reflection\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\DescriptionFactory;
use phpDocumentor\Reflection\Types\Context as TypeContext;
use Webmozart\Assert\Assert;
/**
* Reflection class for a {@}source tag in a Docblock.
*/
final class Source extends BaseTag implements Factory\StaticMethod
{
/** @var string */
protected $name = 'source';
/** @var int The starting line, relative to the structural element's location. */
private $startingLine = 1;
/** @var int|null The number of lines, relative to the starting line. NULL means "to the end". */
private $lineCount = null;
public function __construct($startingLine, $lineCount = null, Description $description = null)
{
Assert::integerish($startingLine);
Assert::nullOrIntegerish($lineCount);
$this->startingLine = (int)$startingLine;
$this->lineCount = $lineCount !== null ? (int)$lineCount : null;
$this->description = $description;
}
/**
* {@inheritdoc}
*/
public static function create($body, DescriptionFactory $descriptionFactory = null, TypeContext $context = null)
{
Assert::stringNotEmpty($body);
Assert::notNull($descriptionFactory);
$startingLine = 1;
$lineCount = null;
$description = null;
// Starting line / Number of lines / Description
if (preg_match('/^([1-9]\d*)\s*(?:((?1))\s+)?(.*)$/sux', $body, $matches)) {
$startingLine = (int)$matches[1];
if (isset($matches[2]) && $matches[2] !== '') {
$lineCount = (int)$matches[2];
}
$description = $matches[3];
}
return new static($startingLine, $lineCount, $descriptionFactory->create($description, $context));
}
/**
* Gets the starting line.
*
* @return int The starting line, relative to the structural element's
* location.
*/
public function getStartingLine()
{
return $this->startingLine;
}
/**
* Returns the number of lines.
*
* @return int|null The number of lines, relative to the starting line. NULL
* means "to the end".
*/
public function getLineCount()
{
return $this->lineCount;
}
public function __toString()
{
return $this->startingLine
. ($this->lineCount !== null ? ' ' . $this->lineCount : '')
. ($this->description ? ' ' . $this->description->render() : '');
}
}
<?php
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\DocBlock\Tags;
use phpDocumentor\Reflection\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\DescriptionFactory;
use phpDocumentor\Reflection\Type;
use phpDocumentor\Reflection\TypeResolver;
use phpDocumentor\Reflection\Types\Context as TypeContext;
use Webmozart\Assert\Assert;
/**
* Reflection class for a {@}throws tag in a Docblock.
*/
final class Throws extends BaseTag implements Factory\StaticMethod
{
protected $name = 'throws';
/** @var Type */
private $type;
public function __construct(Type $type, Description $description = null)
{
$this->type = $type;
$this->description = $description;
}
/**
* {@inheritdoc}
*/
public static function create(
$body,
TypeResolver $typeResolver = null,
DescriptionFactory $descriptionFactory = null,
TypeContext $context = null
) {
Assert::string($body);
Assert::allNotNull([$typeResolver, $descriptionFactory]);
$parts = preg_split('/\s+/Su', $body, 2);
$type = $typeResolver->resolve(isset($parts[0]) ? $parts[0] : '', $context);
$description = $descriptionFactory->create(isset($parts[1]) ? $parts[1] : '', $context);
return new static($type, $description);
}
/**
* Returns the type section of the variable.
*
* @return Type
*/
public function getType()
{
return $this->type;
}
public function __toString()
{
return $this->type . ' ' . $this->description;
}
}
<?php
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\DocBlock\Tags;
use phpDocumentor\Reflection\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\DescriptionFactory;
use phpDocumentor\Reflection\Fqsen;
use phpDocumentor\Reflection\FqsenResolver;
use phpDocumentor\Reflection\Types\Context as TypeContext;
use Webmozart\Assert\Assert;
/**
* Reflection class for a {@}uses tag in a Docblock.
*/
final class Uses extends BaseTag implements Factory\StaticMethod
{
protected $name = 'uses';
/** @var Fqsen */
protected $refers = null;
/**
* Initializes this tag.
*
* @param Fqsen $refers
* @param Description $description
*/
public function __construct(Fqsen $refers, Description $description = null)
{
$this->refers = $refers;
$this->description = $description;
}
/**
* {@inheritdoc}
*/
public static function create(
$body,
FqsenResolver $resolver = null,
DescriptionFactory $descriptionFactory = null,
TypeContext $context = null
) {
Assert::string($body);
Assert::allNotNull([$resolver, $descriptionFactory]);
$parts = preg_split('/\s+/Su', $body, 2);
return new static(
$resolver->resolve($parts[0], $context),
$descriptionFactory->create(isset($parts[1]) ? $parts[1] : '', $context)
);
}
/**
* Returns the structural element this tag refers to.
*
* @return Fqsen
*/
public function getReference()
{
return $this->refers;
}
/**
* Returns a string representation of this tag.
*
* @return string
*/
public function __toString()
{
return $this->refers . ' ' . $this->description->render();
}
}
<?php
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\DocBlock\Tags;
use phpDocumentor\Reflection\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\DescriptionFactory;
use phpDocumentor\Reflection\Type;
use phpDocumentor\Reflection\TypeResolver;
use phpDocumentor\Reflection\Types\Context as TypeContext;
use Webmozart\Assert\Assert;
/**
* Reflection class for a {@}var tag in a Docblock.
*/
class Var_ extends BaseTag implements Factory\StaticMethod
{
/** @var string */
protected $name = 'var';
/** @var Type */
private $type;
/** @var string */
protected $variableName = '';
/**
* @param string $variableName
* @param Type $type
* @param Description $description
*/
public function __construct($variableName, Type $type = null, Description $description = null)
{
Assert::string($variableName);
$this->variableName = $variableName;
$this->type = $type;
$this->description = $description;
}
/**
* {@inheritdoc}
*/
public static function create(
$body,
TypeResolver $typeResolver = null,
DescriptionFactory $descriptionFactory = null,
TypeContext $context = null
) {
Assert::stringNotEmpty($body);
Assert::allNotNull([$typeResolver, $descriptionFactory]);
$parts = preg_split('/(\s+)/Su', $body, 3, PREG_SPLIT_DELIM_CAPTURE);
$type = null;
$variableName = '';
// if the first item that is encountered is not a variable; it is a type
if (isset($parts[0]) && (strlen($parts[0]) > 0) && ($parts[0][0] !== '$')) {
$type = $typeResolver->resolve(array_shift($parts), $context);
array_shift($parts);
}
// if the next item starts with a $ or ...$ it must be the variable name
if (isset($parts[0]) && (strlen($parts[0]) > 0) && ($parts[0][0] == '$')) {
$variableName = array_shift($parts);
array_shift($parts);
if (substr($variableName, 0, 1) === '$') {
$variableName = substr($variableName, 1);
}
}
$description = $descriptionFactory->create(implode('', $parts), $context);
return new static($variableName, $type, $description);
}
/**
* Returns the variable's name.
*
* @return string
*/
public function getVariableName()
{
return $this->variableName;
}
/**
* Returns the variable's type or null if unknown.
*
* @return Type|null
*/
public function getType()
{
return $this->type;
}
/**
* Returns a string representation for this tag.
*
* @return string
*/
public function __toString()
{
return ($this->type ? $this->type . ' ' : '')
. '$' . $this->variableName
. ($this->description ? ' ' . $this->description : '');
}
}
<?php
/**
* phpDocumentor
*
* PHP Version 5.3
*
* @author Vasil Rangelov <boen.robot@gmail.com>
* @copyright 2010-2011 Mike van Riel / Naenius (http://www.naenius.com)
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\DocBlock\Tags;
use phpDocumentor\Reflection\Types\Context as TypeContext;
use phpDocumentor\Reflection\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\DescriptionFactory;
use Webmozart\Assert\Assert;
/**
* Reflection class for a {@}version tag in a Docblock.
*/
final class Version extends BaseTag implements Factory\StaticMethod
{
protected $name = 'version';
/**
* PCRE regular expression matching a version vector.
* Assumes the "x" modifier.
*/
const REGEX_VECTOR = '(?:
# Normal release vectors.
\d\S*
|
# VCS version vectors. Per PHPCS, they are expected to
# follow the form of the VCS name, followed by ":", followed
# by the version vector itself.
# By convention, popular VCSes like CVS, SVN and GIT use "$"
# around the actual version vector.
[^\s\:]+\:\s*\$[^\$]+\$
)';
/** @var string The version vector. */
private $version = '';
public function __construct($version = null, Description $description = null)
{
Assert::nullOrStringNotEmpty($version);
$this->version = $version;
$this->description = $description;
}
/**
* @return static
*/
public static function create($body, DescriptionFactory $descriptionFactory = null, TypeContext $context = null)
{
Assert::nullOrString($body);
if (empty($body)) {
return new static();
}
$matches = [];
if (!preg_match('/^(' . self::REGEX_VECTOR . ')\s*(.+)?$/sux', $body, $matches)) {
return null;
}
return new static(
$matches[1],
$descriptionFactory->create(isset($matches[2]) ? $matches[2] : '', $context)
);
}
/**
* Gets the version section of the tag.
*
* @return string
*/
public function getVersion()
{
return $this->version;
}
/**
* Returns a string representation for this tag.
*
* @return string
*/
public function __toString()
{
return $this->version . ($this->description ? ' ' . $this->description->render() : '');
}
}
<?php
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection;
use phpDocumentor\Reflection\DocBlock\Tag;
use Webmozart\Assert\Assert;
final class DocBlock
{
/** @var string The opening line for this docblock. */
private $summary = '';
/** @var DocBlock\Description The actual description for this docblock. */
private $description = null;
/** @var Tag[] An array containing all the tags in this docblock; except inline. */
private $tags = array();
/** @var Types\Context Information about the context of this DocBlock. */
private $context = null;
/** @var Location Information about the location of this DocBlock. */
private $location = null;
/** @var bool Is this DocBlock (the start of) a template? */
private $isTemplateStart = false;
/** @var bool Does this DocBlock signify the end of a DocBlock template? */
private $isTemplateEnd = false;
/**
* @param string $summary
* @param DocBlock\Description $description
* @param DocBlock\Tag[] $tags
* @param Types\Context $context The context in which the DocBlock occurs.
* @param Location $location The location within the file that this DocBlock occurs in.
* @param bool $isTemplateStart
* @param bool $isTemplateEnd
*/
public function __construct(
$summary = '',
DocBlock\Description $description = null,
array $tags = [],
Types\Context $context = null,
Location $location = null,
$isTemplateStart = false,
$isTemplateEnd = false
)
{
Assert::string($summary);
Assert::boolean($isTemplateStart);
Assert::boolean($isTemplateEnd);
Assert::allIsInstanceOf($tags, Tag::class);
$this->summary = $summary;
$this->description = $description ?: new DocBlock\Description('');
foreach ($tags as $tag) {
$this->addTag($tag);
}
$this->context = $context;
$this->location = $location;
$this->isTemplateEnd = $isTemplateEnd;
$this->isTemplateStart = $isTemplateStart;
}
/**
* @return string
*/
public function getSummary()
{
return $this->summary;
}
/**
* @return DocBlock\Description
*/
public function getDescription()
{
return $this->description;
}
/**
* Returns the current context.
*
* @return Types\Context
*/
public function getContext()
{
return $this->context;
}
/**
* Returns the current location.
*
* @return Location
*/
public function getLocation()
{
return $this->location;
}
/**
* Returns whether this DocBlock is the start of a Template section.
*
* A Docblock may serve as template for a series of subsequent DocBlocks. This is indicated by a special marker
* (`#@+`) that is appended directly after the opening `/**` of a DocBlock.
*
* An example of such an opening is:
*
* ```
* /**#@+
* * My DocBlock
* * /
* ```
*
* The description and tags (not the summary!) are copied onto all subsequent DocBlocks and also applied to all
* elements that follow until another DocBlock is found that contains the closing marker (`#@-`).
*
* @see self::isTemplateEnd() for the check whether a closing marker was provided.
*
* @return boolean
*/
public function isTemplateStart()
{
return $this->isTemplateStart;
}
/**
* Returns whether this DocBlock is the end of a Template section.
*
* @see self::isTemplateStart() for a more complete description of the Docblock Template functionality.
*
* @return boolean
*/
public function isTemplateEnd()
{
return $this->isTemplateEnd;
}
/**
* Returns the tags for this DocBlock.
*
* @return Tag[]
*/
public function getTags()
{
return $this->tags;
}
/**
* Returns an array of tags matching the given name. If no tags are found
* an empty array is returned.
*
* @param string $name String to search by.
*
* @return Tag[]
*/
public function getTagsByName($name)
{
Assert::string($name);
$result = array();
/** @var Tag $tag */
foreach ($this->getTags() as $tag) {
if ($tag->getName() != $name) {
continue;
}
$result[] = $tag;
}
return $result;
}
/**
* Checks if a tag of a certain type is present in this DocBlock.
*
* @param string $name Tag name to check for.
*
* @return bool
*/
public function hasTag($name)
{
Assert::string($name);
/** @var Tag $tag */
foreach ($this->getTags() as $tag) {
if ($tag->getName() == $name) {
return true;
}
}
return false;
}
/**
* Adds a tag to this DocBlock.
*
* @param Tag $tag The tag to add.
*
* @return void
*/
private function addTag(Tag $tag)
{
$this->tags[] = $tag;
}
}
<?php
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection;
use phpDocumentor\Reflection\DocBlock\DescriptionFactory;
use phpDocumentor\Reflection\DocBlock\StandardTagFactory;
use phpDocumentor\Reflection\DocBlock\Tag;
use phpDocumentor\Reflection\DocBlock\TagFactory;
use Webmozart\Assert\Assert;
final class DocBlockFactory implements DocBlockFactoryInterface
{
/** @var DocBlock\DescriptionFactory */
private $descriptionFactory;
/** @var DocBlock\TagFactory */
private $tagFactory;
/**
* Initializes this factory with the required subcontractors.
*
* @param DescriptionFactory $descriptionFactory
* @param TagFactory $tagFactory
*/
public function __construct(DescriptionFactory $descriptionFactory, TagFactory $tagFactory)
{
$this->descriptionFactory = $descriptionFactory;
$this->tagFactory = $tagFactory;
}
/**
* Factory method for easy instantiation.
*
* @param string[] $additionalTags
*
* @return DocBlockFactory
*/
public static function createInstance(array $additionalTags = [])
{
$fqsenResolver = new FqsenResolver();
$tagFactory = new StandardTagFactory($fqsenResolver);
$descriptionFactory = new DescriptionFactory($tagFactory);
$tagFactory->addService($descriptionFactory);
$tagFactory->addService(new TypeResolver($fqsenResolver));
$docBlockFactory = new self($descriptionFactory, $tagFactory);
foreach ($additionalTags as $tagName => $tagHandler) {
$docBlockFactory->registerTagHandler($tagName, $tagHandler);
}
return $docBlockFactory;
}
/**
* @param object|string $docblock A string containing the DocBlock to parse or an object supporting the
* getDocComment method (such as a ReflectionClass object).
* @param Types\Context $context
* @param Location $location
*
* @return DocBlock
*/
public function create($docblock, Types\Context $context = null, Location $location = null)
{
if (is_object($docblock)) {
if (!method_exists($docblock, 'getDocComment')) {
$exceptionMessage = 'Invalid object passed; the given object must support the getDocComment method';
throw new \InvalidArgumentException($exceptionMessage);
}
$docblock = $docblock->getDocComment();
}
Assert::stringNotEmpty($docblock);
if ($context === null) {
$context = new Types\Context('');
}
$parts = $this->splitDocBlock($this->stripDocComment($docblock));
list($templateMarker, $summary, $description, $tags) = $parts;
return new DocBlock(
$summary,
$description ? $this->descriptionFactory->create($description, $context) : null,
array_filter($this->parseTagBlock($tags, $context), function($tag) {
return $tag instanceof Tag;
}),
$context,
$location,
$templateMarker === '#@+',
$templateMarker === '#@-'
);
}
public function registerTagHandler($tagName, $handler)
{
$this->tagFactory->registerTagHandler($tagName, $handler);
}
/**
* Strips the asterisks from the DocBlock comment.
*
* @param string $comment String containing the comment text.
*
* @return string
*/
private function stripDocComment($comment)
{
$comment = trim(preg_replace('#[ \t]*(?:\/\*\*|\*\/|\*)?[ \t]{0,1}(.*)?#u', '$1', $comment));
// reg ex above is not able to remove */ from a single line docblock
if (substr($comment, -2) == '*/') {
$comment = trim(substr($comment, 0, -2));
}
return str_replace(array("\r\n", "\r"), "\n", $comment);
}
/**
* Splits the DocBlock into a template marker, summary, description and block of tags.
*
* @param string $comment Comment to split into the sub-parts.
*
* @author Richard van Velzen (@_richardJ) Special thanks to Richard for the regex responsible for the split.
* @author Mike van Riel <me@mikevanriel.com> for extending the regex with template marker support.
*
* @return string[] containing the template marker (if any), summary, description and a string containing the tags.
*/
private function splitDocBlock($comment)
{
// Performance improvement cheat: if the first character is an @ then only tags are in this DocBlock. This
// method does not split tags so we return this verbatim as the fourth result (tags). This saves us the
// performance impact of running a regular expression
if (strpos($comment, '@') === 0) {
return array('', '', '', $comment);
}
// clears all extra horizontal whitespace from the line endings to prevent parsing issues
$comment = preg_replace('/\h*$/Sum', '', $comment);
/*
* Splits the docblock into a template marker, summary, description and tags section.
*
* - The template marker is empty, #@+ or #@- if the DocBlock starts with either of those (a newline may
* occur after it and will be stripped).
* - The short description is started from the first character until a dot is encountered followed by a
* newline OR two consecutive newlines (horizontal whitespace is taken into account to consider spacing
* errors). This is optional.
* - The long description, any character until a new line is encountered followed by an @ and word
* characters (a tag). This is optional.
* - Tags; the remaining characters
*
* Big thanks to RichardJ for contributing this Regular Expression
*/
preg_match(
'/
\A
# 1. Extract the template marker
(?:(\#\@\+|\#\@\-)\n?)?
# 2. Extract the summary
(?:
(?! @\pL ) # The summary may not start with an @
(
[^\n.]+
(?:
(?! \. \n | \n{2} ) # End summary upon a dot followed by newline or two newlines
[\n.] (?! [ \t]* @\pL ) # End summary when an @ is found as first character on a new line
[^\n.]+ # Include anything else
)*
\.?
)?
)
# 3. Extract the description
(?:
\s* # Some form of whitespace _must_ precede a description because a summary must be there
(?! @\pL ) # The description may not start with an @
(
[^\n]+
(?: \n+
(?! [ \t]* @\pL ) # End description when an @ is found as first character on a new line
[^\n]+ # Include anything else
)*
)
)?
# 4. Extract the tags (anything that follows)
(\s+ [\s\S]*)? # everything that follows
/ux',
$comment,
$matches
);
array_shift($matches);
while (count($matches) < 4) {
$matches[] = '';
}
return $matches;
}
/**
* Creates the tag objects.
*
* @param string $tags Tag block to parse.
* @param Types\Context $context Context of the parsed Tag
*
* @return DocBlock\Tag[]
*/
private function parseTagBlock($tags, Types\Context $context)
{
$tags = $this->filterTagBlock($tags);
if (!$tags) {
return [];
}
$result = $this->splitTagBlockIntoTagLines($tags);
foreach ($result as $key => $tagLine) {
$result[$key] = $this->tagFactory->create(trim($tagLine), $context);
}
return $result;
}
/**
* @param string $tags
*
* @return string[]
*/
private function splitTagBlockIntoTagLines($tags)
{
$result = array();
foreach (explode("\n", $tags) as $tag_line) {
if (isset($tag_line[0]) && ($tag_line[0] === '@')) {
$result[] = $tag_line;
} else {
$result[count($result) - 1] .= "\n" . $tag_line;
}
}
return $result;
}
/**
* @param $tags
* @return string
*/
private function filterTagBlock($tags)
{
$tags = trim($tags);
if (!$tags) {
return null;
}
if ('@' !== $tags[0]) {
// @codeCoverageIgnoreStart
// Can't simulate this; this only happens if there is an error with the parsing of the DocBlock that
// we didn't foresee.
throw new \LogicException('A tag block started with text instead of an at-sign(@): ' . $tags);
// @codeCoverageIgnoreEnd
}
return $tags;
}
}
<?php
namespace phpDocumentor\Reflection;
interface DocBlockFactoryInterface
{
/**
* Factory method for easy instantiation.
*
* @param string[] $additionalTags
*
* @return DocBlockFactory
*/
public static function createInstance(array $additionalTags = []);
/**
* @param string $docblock
* @param Types\Context $context
* @param Location $location
*
* @return DocBlock
*/
public function create($docblock, Types\Context $context = null, Location $location = null);
}
<?php
use phpDocumentor\Reflection\TypeResolver;
require '../vendor/autoload.php';
$typeResolver = new TypeResolver();
// Will yield an object of type phpDocumentor\Types\Compound
var_export($typeResolver->resolve('string|integer'));
// Will return the string "string|int"
var_dump((string)$typeResolver->resolve('string|integer'));
<?php
use phpDocumentor\Reflection\Types\Context;
use phpDocumentor\Reflection\TypeResolver;
require '../vendor/autoload.php';
$typeResolver = new TypeResolver();
// Will use the namespace and aliases to resolve to \phpDocumentor\Types\Resolver|Mockery\MockInterface
$context = new Context('\phpDocumentor', [ 'm' => 'Mockery' ]);
var_dump((string)$typeResolver->resolve('Types\Resolver|m\MockInterface', $context));
<?php
use phpDocumentor\Reflection\Types\Context;
use phpDocumentor\Reflection\FqsenResolver;
require '../vendor/autoload.php';
$fqsenResolver = new FqsenResolver();
// Will use the namespace and aliases to resolve to a Fqsen object
$context = new Context('\phpDocumentor\Types');
// Method named: \phpDocumentor\Types\Types\Resolver::resolveFqsen()
var_dump((string)$fqsenResolver->resolve('Types\Resolver::resolveFqsen()', $context));
// Property named: \phpDocumentor\Types\Types\Resolver::$keyWords
var_dump((string)$fqsenResolver->resolve('Types\Resolver::$keyWords', $context));
<?php
use phpDocumentor\Reflection\FqsenResolver;
use phpDocumentor\Reflection\TypeResolver;
use phpDocumentor\Reflection\Types\ContextFactory;
require '../vendor/autoload.php';
require 'Classy.php';
$typeResolver = new TypeResolver();
$fqsenResolver = new FqsenResolver();
$contextFactory = new ContextFactory();
$context = $contextFactory->createFromReflector(new ReflectionClass('My\\Example\\Classy'));
// Class named: \phpDocumentor\Reflection\Types\Resolver
var_dump((string)$typeResolver->resolve('Types\Resolver', $context));
// String
var_dump((string)$typeResolver->resolve('string', $context));
// Property named: \phpDocumentor\Reflection\Types\Resolver::$keyWords
var_dump((string)$fqsenResolver->resolve('Types\Resolver::$keyWords', $context));
// Class named: \My\Example\string
// - Shows the difference between the FqsenResolver and TypeResolver; the FqsenResolver will assume
// that the given value is not a type but most definitely a reference to another element. This is
// because conflicts between type keywords and class names can exist and if you know a reference
// is not a type but an element you can force that keywords are resolved.
var_dump((string)$fqsenResolver->resolve('string', $context));
<?php
use phpDocumentor\Reflection\FqsenResolver;
use phpDocumentor\Reflection\TypeResolver;
use phpDocumentor\Reflection\Types\ContextFactory;
require '../vendor/autoload.php';
require 'Classy.php';
$typeResolver = new TypeResolver();
$fqsenResolver = new FqsenResolver();
$contextFactory = new ContextFactory();
$context = $contextFactory->createFromReflector(new ReflectionMethod('My\\Example\\Classy', '__construct'));
// Class named: \phpDocumentor\Reflection\Types\Resolver
var_dump((string)$typeResolver->resolve('Types\Resolver', $context));
// String
var_dump((string)$typeResolver->resolve('string', $context));
// Property named: \phpDocumentor\Reflection\Types\Resolver::$keyWords
var_dump((string)$fqsenResolver->resolve('Types\Resolver::$keyWords', $context));
// Class named: \My\Example\string
// - Shows the difference between the FqsenResolver and TypeResolver; the FqsenResolver will assume
// that the given value is not a type but most definitely a reference to another element. This is
// because conflicts between type keywords and class names can exist and if you know a reference
// is not a type but an element you can force that keywords are resolved.
var_dump((string)$fqsenResolver->resolve('string', $context));
<?php
use phpDocumentor\Reflection\FqsenResolver;
use phpDocumentor\Reflection\TypeResolver;
use phpDocumentor\Reflection\Types\ContextFactory;
require '../vendor/autoload.php';
$typeResolver = new TypeResolver();
$fqsenResolver = new FqsenResolver();
$contextFactory = new ContextFactory();
$context = $contextFactory->createForNamespace('My\Example', file_get_contents('Classy.php'));
// Class named: \phpDocumentor\Reflection\Types\Resolver
var_dump((string)$typeResolver->resolve('Types\Resolver', $context));
// String
var_dump((string)$typeResolver->resolve('string', $context));
// Property named: \phpDocumentor\Reflection\Types\Resolver::$keyWords
var_dump((string)$fqsenResolver->resolve('Types\Resolver::$keyWords', $context));
<?php
namespace My\Example;
use Mockery as m;
use phpDocumentor\Reflection\Types;
class Classy
{
/**
* @var Types\Context
*/
public function __construct($context)
{
}
}
<?php
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection;
use phpDocumentor\Reflection\Types\Context;
class FqsenResolver
{
/** @var string Definition of the NAMESPACE operator in PHP */
const OPERATOR_NAMESPACE = '\\';
public function resolve($fqsen, Context $context = null)
{
if ($context === null) {
$context = new Context('');
}
if ($this->isFqsen($fqsen)) {
return new Fqsen($fqsen);
}
return $this->resolvePartialStructuralElementName($fqsen, $context);
}
/**
* Tests whether the given type is a Fully Qualified Structural Element Name.
*
* @param string $type
*
* @return bool
*/
private function isFqsen($type)
{
return strpos($type, self::OPERATOR_NAMESPACE) === 0;
}
/**
* Resolves a partial Structural Element Name (i.e. `Reflection\DocBlock`) to its FQSEN representation
* (i.e. `\phpDocumentor\Reflection\DocBlock`) based on the Namespace and aliases mentioned in the Context.
*
* @param string $type
* @param Context $context
*
* @return Fqsen
*/
private function resolvePartialStructuralElementName($type, Context $context)
{
$typeParts = explode(self::OPERATOR_NAMESPACE, $type, 2);
$namespaceAliases = $context->getNamespaceAliases();
// if the first segment is not an alias; prepend namespace name and return
if (!isset($namespaceAliases[$typeParts[0]])) {
$namespace = $context->getNamespace();
if ('' !== $namespace) {
$namespace .= self::OPERATOR_NAMESPACE;
}
return new Fqsen(self::OPERATOR_NAMESPACE . $namespace . $type);
}
$typeParts[0] = $namespaceAliases[$typeParts[0]];
return new Fqsen(self::OPERATOR_NAMESPACE . implode(self::OPERATOR_NAMESPACE, $typeParts));
}
}
<?php
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection;
interface Type
{
public function __toString();
}
<?php
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection;
use phpDocumentor\Reflection\Types\Array_;
use phpDocumentor\Reflection\Types\Compound;
use phpDocumentor\Reflection\Types\Context;
use phpDocumentor\Reflection\Types\Object_;
final class TypeResolver
{
/** @var string Definition of the ARRAY operator for types */
const OPERATOR_ARRAY = '[]';
/** @var string Definition of the NAMESPACE operator in PHP */
const OPERATOR_NAMESPACE = '\\';
/** @var string[] List of recognized keywords and unto which Value Object they map */
private $keywords = array(
'string' => 'phpDocumentor\Reflection\Types\String_',
'int' => 'phpDocumentor\Reflection\Types\Integer',
'integer' => 'phpDocumentor\Reflection\Types\Integer',
'bool' => 'phpDocumentor\Reflection\Types\Boolean',
'boolean' => 'phpDocumentor\Reflection\Types\Boolean',
'float' => 'phpDocumentor\Reflection\Types\Float_',
'double' => 'phpDocumentor\Reflection\Types\Float_',
'object' => 'phpDocumentor\Reflection\Types\Object_',
'mixed' => 'phpDocumentor\Reflection\Types\Mixed',
'array' => 'phpDocumentor\Reflection\Types\Array_',
'resource' => 'phpDocumentor\Reflection\Types\Resource',
'void' => 'phpDocumentor\Reflection\Types\Void_',
'null' => 'phpDocumentor\Reflection\Types\Null_',
'scalar' => 'phpDocumentor\Reflection\Types\Scalar',
'callback' => 'phpDocumentor\Reflection\Types\Callable_',
'callable' => 'phpDocumentor\Reflection\Types\Callable_',
'false' => 'phpDocumentor\Reflection\Types\Boolean',
'true' => 'phpDocumentor\Reflection\Types\Boolean',
'self' => 'phpDocumentor\Reflection\Types\Self_',
'$this' => 'phpDocumentor\Reflection\Types\This',
'static' => 'phpDocumentor\Reflection\Types\Static_'
);
/** @var FqsenResolver */
private $fqsenResolver;
/**
* Initializes this TypeResolver with the means to create and resolve Fqsen objects.
*
* @param FqsenResolver $fqsenResolver
*/
public function __construct(FqsenResolver $fqsenResolver = null)
{
$this->fqsenResolver = $fqsenResolver ?: new FqsenResolver();
}
/**
* Analyzes the given type and returns the FQCN variant.
*
* When a type is provided this method checks whether it is not a keyword or
* Fully Qualified Class Name. If so it will use the given namespace and
* aliases to expand the type to a FQCN representation.
*
* This method only works as expected if the namespace and aliases are set;
* no dynamic reflection is being performed here.
*
* @param string $type The relative or absolute type.
* @param Context $context
*
* @uses Context::getNamespace() to determine with what to prefix the type name.
* @uses Context::getNamespaceAliases() to check whether the first part of the relative type name should not be
* replaced with another namespace.
*
* @return Type|null
*/
public function resolve($type, Context $context = null)
{
if (!is_string($type)) {
throw new \InvalidArgumentException(
'Attempted to resolve type but it appeared not to be a string, received: ' . var_export($type, true)
);
}
$type = trim($type);
if (!$type) {
throw new \InvalidArgumentException('Attempted to resolve "' . $type . '" but it appears to be empty');
}
if ($context === null) {
$context = new Context('');
}
switch (true) {
case $this->isKeyword($type):
return $this->resolveKeyword($type);
case ($this->isCompoundType($type)):
return $this->resolveCompoundType($type, $context);
case $this->isTypedArray($type):
return $this->resolveTypedArray($type, $context);
case $this->isFqsen($type):
return $this->resolveTypedObject($type);
case $this->isPartialStructuralElementName($type):
return $this->resolveTypedObject($type, $context);
// @codeCoverageIgnoreStart
default:
// I haven't got the foggiest how the logic would come here but added this as a defense.
throw new \RuntimeException(
'Unable to resolve type "' . $type . '", there is no known method to resolve it'
);
}
// @codeCoverageIgnoreEnd
}
/**
* Adds a keyword to the list of Keywords and associates it with a specific Value Object.
*
* @param string $keyword
* @param string $typeClassName
*
* @return void
*/
public function addKeyword($keyword, $typeClassName)
{
if (!class_exists($typeClassName)) {
throw new \InvalidArgumentException(
'The Value Object that needs to be created with a keyword "' . $keyword . '" must be an existing class'
. ' but we could not find the class ' . $typeClassName
);
}
if (!in_array(Type::class, class_implements($typeClassName))) {
throw new \InvalidArgumentException(
'The class "' . $typeClassName . '" must implement the interface "phpDocumentor\Reflection\Type"'
);
}
$this->keywords[$keyword] = $typeClassName;
}
/**
* Detects whether the given type represents an array.
*
* @param string $type A relative or absolute type as defined in the phpDocumentor documentation.
*
* @return bool
*/
private function isTypedArray($type)
{
return substr($type, -2) === self::OPERATOR_ARRAY;
}
/**
* Detects whether the given type represents a PHPDoc keyword.
*
* @param string $type A relative or absolute type as defined in the phpDocumentor documentation.
*
* @return bool
*/
private function isKeyword($type)
{
return in_array(strtolower($type), array_keys($this->keywords), true);
}
/**
* Detects whether the given type represents a relative structural element name.
*
* @param string $type A relative or absolute type as defined in the phpDocumentor documentation.
*
* @return bool
*/
private function isPartialStructuralElementName($type)
{
return ($type[0] !== self::OPERATOR_NAMESPACE) && !$this->isKeyword($type);
}
/**
* Tests whether the given type is a Fully Qualified Structural Element Name.
*
* @param string $type
*
* @return bool
*/
private function isFqsen($type)
{
return strpos($type, self::OPERATOR_NAMESPACE) === 0;
}
/**
* Tests whether the given type is a compound type (i.e. `string|int`).
*
* @param string $type
*
* @return bool
*/
private function isCompoundType($type)
{
return strpos($type, '|') !== false;
}
/**
* Resolves the given typed array string (i.e. `string[]`) into an Array object with the right types set.
*
* @param string $type
* @param Context $context
*
* @return Array_
*/
private function resolveTypedArray($type, Context $context)
{
return new Array_($this->resolve(substr($type, 0, -2), $context));
}
/**
* Resolves the given keyword (such as `string`) into a Type object representing that keyword.
*
* @param string $type
*
* @return Type
*/
private function resolveKeyword($type)
{
$className = $this->keywords[strtolower($type)];
return new $className();
}
/**
* Resolves the given FQSEN string into an FQSEN object.
*
* @param string $type
*
* @return Object_
*/
private function resolveTypedObject($type, Context $context = null)
{
return new Object_($this->fqsenResolver->resolve($type, $context));
}
/**
* Resolves a compound type (i.e. `string|int`) into the appropriate Type objects or FQSEN.
*
* @param string $type
* @param Context $context
*
* @return Compound
*/
private function resolveCompoundType($type, Context $context)
{
$types = [];
foreach (explode('|', $type) as $part) {
$types[] = $this->resolve($part, $context);
}
return new Compound($types);
}
}
<?php
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Types;
use phpDocumentor\Reflection\Fqsen;
use phpDocumentor\Reflection\Type;
/**
* Represents an array type as described in the PSR-5, the PHPDoc Standard.
*
* An array can be represented in two forms:
*
* 1. Untyped (`array`), where the key and value type is unknown and hence classified as 'Mixed'.
* 2. Types (`string[]`), where the value type is provided by preceding an opening and closing square bracket with a
* type name.
*/
final class Array_ implements Type
{
/** @var Type */
private $valueType;
/** @var Type */
private $keyType;
/**
* Initializes this representation of an array with the given Type or Fqsen.
*
* @param Type $valueType
* @param Type $keyType
*/
public function __construct(Type $valueType = null, Type $keyType = null)
{
if ($keyType === null) {
$keyType = new Compound([ new String_(), new Integer() ]);
}
if ($valueType === null) {
$valueType = new Mixed();
}
$this->valueType = $valueType;
$this->keyType = $keyType;
}
/**
* Returns the type for the keys of this array.
*
* @return Type
*/
public function getKeyType()
{
return $this->keyType;
}
/**
* Returns the value for the keys of this array.
*
* @return Type
*/
public function getValueType()
{
return $this->valueType;
}
/**
* Returns a rendered output of the Type as it would be used in a DocBlock.
*
* @return string
*/
public function __toString()
{
if ($this->valueType instanceof Mixed) {
return 'array';
}
return $this->valueType . '[]';
}
}
<?php
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Types;
use phpDocumentor\Reflection\Type;
/**
* Value Object representing a Boolean type.
*/
final class Boolean implements Type
{
/**
* Returns a rendered output of the Type as it would be used in a DocBlock.
*
* @return string
*/
public function __toString()
{
return 'bool';
}
}
<?php
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Types;
use phpDocumentor\Reflection\Type;
/**
* Value Object representing a Callable type.
*/
final class Callable_ implements Type
{
/**
* Returns a rendered output of the Type as it would be used in a DocBlock.
*
* @return string
*/
public function __toString()
{
return 'callable';
}
}
<?php
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Types;
use phpDocumentor\Reflection\Type;
/**
* Value Object representing a Compound Type.
*
* A Compound Type is not so much a special keyword or object reference but is a series of Types that are separated
* using an OR operator (`|`). This combination of types signifies that whatever is associated with this compound type
* may contain a value with any of the given types.
*/
final class Compound implements Type
{
/** @var Type[] */
private $types = [];
/**
* Initializes a compound type (i.e. `string|int`) and tests if the provided types all implement the Type interface.
*
* @param Type[] $types
*/
public function __construct(array $types)
{
foreach ($types as $type) {
if (!$type instanceof Type) {
throw new \InvalidArgumentException('A compound type can only have other types as elements');
}
}
$this->types = $types;
}
/**
* Returns the type at the given index.
*
* @param integer $index
*
* @return Type|null
*/
public function get($index)
{
if (!$this->has($index)) {
return null;
}
return $this->types[$index];
}
/**
* Tests if this compound type has a type with the given index.
*
* @param integer $index
*
* @return bool
*/
public function has($index)
{
return isset($this->types[$index]);
}
/**
* Returns a rendered output of the Type as it would be used in a DocBlock.
*
* @return string
*/
public function __toString()
{
return implode('|', $this->types);
}
}
<?php
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Types;
/**
* Provides information about the Context in which the DocBlock occurs that receives this context.
*
* A DocBlock does not know of its own accord in which namespace it occurs and which namespace aliases are applicable
* for the block of code in which it is in. This information is however necessary to resolve Class names in tags since
* you can provide a short form or make use of namespace aliases.
*
* The phpDocumentor Reflection component knows how to create this class but if you use the DocBlock parser from your
* own application it is possible to generate a Context class using the ContextFactory; this will analyze the file in
* which an associated class resides for its namespace and imports.
*
* @see ContextFactory::createFromClassReflector()
* @see ContextFactory::createForNamespace()
*/
final class Context
{
/** @var string The current namespace. */
private $namespace = '';
/** @var array List of namespace aliases => Fully Qualified Namespace. */
private $namespaceAliases = [];
/**
* Initializes the new context and normalizes all passed namespaces to be in Qualified Namespace Name (QNN)
* format (without a preceding `\`).
*
* @param string $namespace The namespace where this DocBlock resides in.
* @param array $namespaceAliases List of namespace aliases => Fully Qualified Namespace.
*/
public function __construct($namespace, array $namespaceAliases = [])
{
$this->namespace = ('global' !== $namespace && 'default' !== $namespace)
? trim((string)$namespace, '\\')
: '';
foreach ($namespaceAliases as $alias => $fqnn) {
if ($fqnn[0] === '\\') {
$fqnn = substr($fqnn, 1);
}
if ($fqnn[count($fqnn)-1] === '\\') {
$fqnn = substr($fqnn, 0, -1);
}
$namespaceAliases[$alias] = $fqnn;
}
$this->namespaceAliases = $namespaceAliases;
}
/**
* Returns the Qualified Namespace Name (thus without `\` in front) where the associated element is in.
*
* @return string
*/
public function getNamespace()
{
return $this->namespace;
}
/**
* Returns a list of Qualified Namespace Names (thus without `\` in front) that are imported, the keys represent
* the alias for the imported Namespace.
*
* @return string[]
*/
public function getNamespaceAliases()
{
return $this->namespaceAliases;
}
}
<?php
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Types;
/**
* Convenience class to create a Context for DocBlocks when not using the Reflection Component of phpDocumentor.
*
* For a DocBlock to be able to resolve types that use partial namespace names or rely on namespace imports we need to
* provide a bit of context so that the DocBlock can read that and based on it decide how to resolve the types to
* Fully Qualified names.
*
* @see Context for more information.
*/
final class ContextFactory
{
/** The literal used at the end of a use statement. */
const T_LITERAL_END_OF_USE = ';';
/** The literal used between sets of use statements */
const T_LITERAL_USE_SEPARATOR = ',';
/**
* Build a Context given a Class Reflection.
*
* @param \ReflectionClass $reflector
*
* @see Context for more information on Contexts.
*
* @return Context
*/
public function createFromReflector(\Reflector $reflector)
{
if (method_exists($reflector, 'getDeclaringClass')) {
$reflector = $reflector->getDeclaringClass();
}
$fileName = $reflector->getFileName();
$namespace = $reflector->getNamespaceName();
if (file_exists($fileName)) {
return $this->createForNamespace($namespace, file_get_contents($fileName));
}
return new Context($namespace, []);
}
/**
* Build a Context for a namespace in the provided file contents.
*
* @param string $namespace It does not matter if a `\` precedes the namespace name, this method first normalizes.
* @param string $fileContents the file's contents to retrieve the aliases from with the given namespace.
*
* @see Context for more information on Contexts.
*
* @return Context
*/
public function createForNamespace($namespace, $fileContents)
{
$namespace = trim($namespace, '\\');
$useStatements = [];
$currentNamespace = '';
$tokens = new \ArrayIterator(token_get_all($fileContents));
while ($tokens->valid()) {
switch ($tokens->current()[0]) {
case T_NAMESPACE:
$currentNamespace = $this->parseNamespace($tokens);
break;
case T_CLASS:
// Fast-forward the iterator through the class so that any
// T_USE tokens found within are skipped - these are not
// valid namespace use statements so should be ignored.
$braceLevel = 0;
$firstBraceFound = false;
while ($tokens->valid() && ($braceLevel > 0 || !$firstBraceFound)) {
if ($tokens->current() === '{'
|| $tokens->current()[0] === T_CURLY_OPEN
|| $tokens->current()[0] === T_DOLLAR_OPEN_CURLY_BRACES) {
if (!$firstBraceFound) {
$firstBraceFound = true;
}
$braceLevel++;
}
if ($tokens->current() === '}') {
$braceLevel--;
}
$tokens->next();
}
break;
case T_USE:
if ($currentNamespace === $namespace) {
$useStatements = array_merge($useStatements, $this->parseUseStatement($tokens));
}
break;
}
$tokens->next();
}
return new Context($namespace, $useStatements);
}
/**
* Deduce the name from tokens when we are at the T_NAMESPACE token.
*
* @param \ArrayIterator $tokens
*
* @return string
*/
private function parseNamespace(\ArrayIterator $tokens)
{
// skip to the first string or namespace separator
$this->skipToNextStringOrNamespaceSeparator($tokens);
$name = '';
while ($tokens->valid() && ($tokens->current()[0] === T_STRING || $tokens->current()[0] === T_NS_SEPARATOR)
) {
$name .= $tokens->current()[1];
$tokens->next();
}
return $name;
}
/**
* Deduce the names of all imports when we are at the T_USE token.
*
* @param \ArrayIterator $tokens
*
* @return string[]
*/
private function parseUseStatement(\ArrayIterator $tokens)
{
$uses = [];
$continue = true;
while ($continue) {
$this->skipToNextStringOrNamespaceSeparator($tokens);
list($alias, $fqnn) = $this->extractUseStatement($tokens);
$uses[$alias] = $fqnn;
if ($tokens->current()[0] === self::T_LITERAL_END_OF_USE) {
$continue = false;
}
}
return $uses;
}
/**
* Fast-forwards the iterator as longs as we don't encounter a T_STRING or T_NS_SEPARATOR token.
*
* @param \ArrayIterator $tokens
*
* @return void
*/
private function skipToNextStringOrNamespaceSeparator(\ArrayIterator $tokens)
{
while ($tokens->valid() && ($tokens->current()[0] !== T_STRING) && ($tokens->current()[0] !== T_NS_SEPARATOR)) {
$tokens->next();
}
}
/**
* Deduce the namespace name and alias of an import when we are at the T_USE token or have not reached the end of
* a USE statement yet.
*
* @param \ArrayIterator $tokens
*
* @return string
*/
private function extractUseStatement(\ArrayIterator $tokens)
{
$result = [''];
while ($tokens->valid()
&& ($tokens->current()[0] !== self::T_LITERAL_USE_SEPARATOR)
&& ($tokens->current()[0] !== self::T_LITERAL_END_OF_USE)
) {
if ($tokens->current()[0] === T_AS) {
$result[] = '';
}
if ($tokens->current()[0] === T_STRING || $tokens->current()[0] === T_NS_SEPARATOR) {
$result[count($result) - 1] .= $tokens->current()[1];
}
$tokens->next();
}
if (count($result) == 1) {
$backslashPos = strrpos($result[0], '\\');
if (false !== $backslashPos) {
$result[] = substr($result[0], $backslashPos + 1);
} else {
$result[] = $result[0];
}
}
return array_reverse($result);
}
}
<?php
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Types;
use phpDocumentor\Reflection\Type;
/**
* Value Object representing a Float.
*/
final class Float_ implements Type
{
/**
* Returns a rendered output of the Type as it would be used in a DocBlock.
*
* @return string
*/
public function __toString()
{
return 'float';
}
}
<?php
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Types;
use phpDocumentor\Reflection\Type;
final class Integer implements Type
{
/**
* Returns a rendered output of the Type as it would be used in a DocBlock.
*
* @return string
*/
public function __toString()
{
return 'int';
}
}
<?php
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Types;
use phpDocumentor\Reflection\Type;
/**
* Value Object representing an unknown, or mixed, type.
*/
final class Mixed implements Type
{
/**
* Returns a rendered output of the Type as it would be used in a DocBlock.
*
* @return string
*/
public function __toString()
{
return 'mixed';
}
}
<?php
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Types;
use phpDocumentor\Reflection\Type;
/**
* Value Object representing a null value or type.
*/
final class Null_ implements Type
{
/**
* Returns a rendered output of the Type as it would be used in a DocBlock.
*
* @return string
*/
public function __toString()
{
return 'null';
}
}
<?php
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Types;
use phpDocumentor\Reflection\Fqsen;
use phpDocumentor\Reflection\Type;
/**
* Value Object representing an object.
*
* An object can be either typed or untyped. When an object is typed it means that it has an identifier, the FQSEN,
* pointing to an element in PHP. Object types that are untyped do not refer to a specific class but represent objects
* in general.
*/
final class Object_ implements Type
{
/** @var Fqsen|null */
private $fqsen;
/**
* Initializes this object with an optional FQSEN, if not provided this object is considered 'untyped'.
*
* @param Fqsen $fqsen
*/
public function __construct(Fqsen $fqsen = null)
{
if (strpos((string)$fqsen, '::') !== false || strpos((string)$fqsen, '()') !== false) {
throw new \InvalidArgumentException(
'Object types can only refer to a class, interface or trait but a method, function, constant or '
. 'property was received: ' . (string)$fqsen
);
}
$this->fqsen = $fqsen;
}
/**
* Returns the FQSEN associated with this object.
*
* @return Fqsen|null
*/
public function getFqsen()
{
return $this->fqsen;
}
/**
* Returns a rendered output of the Type as it would be used in a DocBlock.
*
* @return string
*/
public function __toString()
{
if ($this->fqsen) {
return (string)$this->fqsen;
}
return 'object';
}
}
<?php
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Types;
use phpDocumentor\Reflection\Type;
/**
* Value Object representing the 'resource' Type.
*/
final class Resource implements Type
{
/**
* Returns a rendered output of the Type as it would be used in a DocBlock.
*
* @return string
*/
public function __toString()
{
return 'resource';
}
}
<?php
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Types;
use phpDocumentor\Reflection\Type;
/**
* Value Object representing the 'scalar' pseudo-type, which is either a string, integer, float or boolean.
*/
final class Scalar implements Type
{
/**
* Returns a rendered output of the Type as it would be used in a DocBlock.
*
* @return string
*/
public function __toString()
{
return 'scalar';
}
}
<?php
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Types;
use phpDocumentor\Reflection\Type;
/**
* Value Object representing the 'self' type.
*
* Self, as a Type, represents the class in which the associated element was defined.
*/
final class Self_ implements Type
{
/**
* Returns a rendered output of the Type as it would be used in a DocBlock.
*
* @return string
*/
public function __toString()
{
return 'self';
}
}
<?php
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Types;
use phpDocumentor\Reflection\Type;
/**
* Value Object representing the 'static' type.
*
* Self, as a Type, represents the class in which the associated element was called. This differs from self as self does
* not take inheritance into account but static means that the return type is always that of the class of the called
* element.
*
* See the documentation on late static binding in the PHP Documentation for more information on the difference between
* static and self.
*/
final class Static_ implements Type
{
/**
* Returns a rendered output of the Type as it would be used in a DocBlock.
*
* @return string
*/
public function __toString()
{
return 'static';
}
}
<?php
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Types;
use phpDocumentor\Reflection\Type;
/**
* Value Object representing the type 'string'.
*/
final class String_ implements Type
{
/**
* Returns a rendered output of the Type as it would be used in a DocBlock.
*
* @return string
*/
public function __toString()
{
return 'string';
}
}
<?php
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Types;
use phpDocumentor\Reflection\Type;
/**
* Value Object representing the '$this' pseudo-type.
*
* $this, as a Type, represents the instance of the class associated with the element as it was called. $this is
* commonly used when documenting fluent interfaces since it represents that the same object is returned.
*/
final class This implements Type
{
/**
* Returns a rendered output of the Type as it would be used in a DocBlock.
*
* @return string
*/
public function __toString()
{
return '$this';
}
}
<?php
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Types;
use phpDocumentor\Reflection\Type;
/**
* Value Object representing the pseudo-type 'void'.
*
* Void is generally only used when working with return types as it signifies that the method intentionally does not
* return any value.
*/
final class Void_ implements Type
{
/**
* Returns a rendered output of the Type as it would be used in a DocBlock.
*
* @return string
*/
public function __toString()
{
return 'void';
}
}
<?php
namespace Psr\Log;
/**
* This is a simple Logger implementation that other Loggers can inherit from.
*
* It simply delegates all log-level-specific methods to the `log` method to
* reduce boilerplate code that a simple Logger that does the same thing with
* messages regardless of the error level has to implement.
*/
abstract class AbstractLogger implements LoggerInterface
{
/**
* System is unusable.
*
* @param string $message
* @param array $context
*
* @return void
*/
public function emergency($message, array $context = array())
{
$this->log(LogLevel::EMERGENCY, $message, $context);
}
/**
* Action must be taken immediately.
*
* Example: Entire website down, database unavailable, etc. This should
* trigger the SMS alerts and wake you up.
*
* @param string $message
* @param array $context
*
* @return void
*/
public function alert($message, array $context = array())
{
$this->log(LogLevel::ALERT, $message, $context);
}
/**
* Critical conditions.
*
* Example: Application component unavailable, unexpected exception.
*
* @param string $message
* @param array $context
*
* @return void
*/
public function critical($message, array $context = array())
{
$this->log(LogLevel::CRITICAL, $message, $context);
}
/**
* Runtime errors that do not require immediate action but should typically
* be logged and monitored.
*
* @param string $message
* @param array $context
*
* @return void
*/
public function error($message, array $context = array())
{
$this->log(LogLevel::ERROR, $message, $context);
}
/**
* Exceptional occurrences that are not errors.
*
* Example: Use of deprecated APIs, poor use of an API, undesirable things
* that are not necessarily wrong.
*
* @param string $message
* @param array $context
*
* @return void
*/
public function warning($message, array $context = array())
{
$this->log(LogLevel::WARNING, $message, $context);
}
/**
* Normal but significant events.
*
* @param string $message
* @param array $context
*
* @return void
*/
public function notice($message, array $context = array())
{
$this->log(LogLevel::NOTICE, $message, $context);
}
/**
* Interesting events.
*
* Example: User logs in, SQL logs.
*
* @param string $message
* @param array $context
*
* @return void
*/
public function info($message, array $context = array())
{
$this->log(LogLevel::INFO, $message, $context);
}
/**
* Detailed debug information.
*
* @param string $message
* @param array $context
*
* @return void
*/
public function debug($message, array $context = array())
{
$this->log(LogLevel::DEBUG, $message, $context);
}
}
<?php
namespace Psr\Log;
class InvalidArgumentException extends \InvalidArgumentException
{
}
<?php
namespace Psr\Log;
/**
* Describes a logger-aware instance.
*/
interface LoggerAwareInterface
{
/**
* Sets a logger instance on the object.
*
* @param LoggerInterface $logger
*
* @return void
*/
public function setLogger(LoggerInterface $logger);
}
<?php
namespace Psr\Log;
/**
* Basic Implementation of LoggerAwareInterface.
*/
trait LoggerAwareTrait
{
/**
* The logger instance.
*
* @var LoggerInterface
*/
protected $logger;
/**
* Sets a logger.
*
* @param LoggerInterface $logger
*/
public function setLogger(LoggerInterface $logger)
{
$this->logger = $logger;
}
}
<?php
namespace Psr\Log;
/**
* Describes a logger instance.
*
* The message MUST be a string or object implementing __toString().
*
* The message MAY contain placeholders in the form: {foo} where foo
* will be replaced by the context data in key "foo".
*
* The context array can contain arbitrary data. The only assumption that
* can be made by implementors is that if an Exception instance is given
* to produce a stack trace, it MUST be in a key named "exception".
*
* See https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md
* for the full interface specification.
*/
interface LoggerInterface
{
/**
* System is unusable.
*
* @param string $message
* @param array $context
*
* @return void
*/
public function emergency($message, array $context = array());
/**
* Action must be taken immediately.
*
* Example: Entire website down, database unavailable, etc. This should
* trigger the SMS alerts and wake you up.
*
* @param string $message
* @param array $context
*
* @return void
*/
public function alert($message, array $context = array());
/**
* Critical conditions.
*
* Example: Application component unavailable, unexpected exception.
*
* @param string $message
* @param array $context
*
* @return void
*/
public function critical($message, array $context = array());
/**
* Runtime errors that do not require immediate action but should typically
* be logged and monitored.
*
* @param string $message
* @param array $context
*
* @return void
*/
public function error($message, array $context = array());
/**
* Exceptional occurrences that are not errors.
*
* Example: Use of deprecated APIs, poor use of an API, undesirable things
* that are not necessarily wrong.
*
* @param string $message
* @param array $context
*
* @return void
*/
public function warning($message, array $context = array());
/**
* Normal but significant events.
*
* @param string $message
* @param array $context
*
* @return void
*/
public function notice($message, array $context = array());
/**
* Interesting events.
*
* Example: User logs in, SQL logs.
*
* @param string $message
* @param array $context
*
* @return void
*/
public function info($message, array $context = array());
/**
* Detailed debug information.
*
* @param string $message
* @param array $context
*
* @return void
*/
public function debug($message, array $context = array());
/**
* Logs with an arbitrary level.
*
* @param mixed $level
* @param string $message
* @param array $context
*
* @return void
*/
public function log($level, $message, array $context = array());
}
<?php
namespace Psr\Log;
/**
* This is a simple Logger trait that classes unable to extend AbstractLogger
* (because they extend another class, etc) can include.
*
* It simply delegates all log-level-specific methods to the `log` method to
* reduce boilerplate code that a simple Logger that does the same thing with
* messages regardless of the error level has to implement.
*/
trait LoggerTrait
{
/**
* System is unusable.
*
* @param string $message
* @param array $context
*
* @return void
*/
public function emergency($message, array $context = array())
{
$this->log(LogLevel::EMERGENCY, $message, $context);
}
/**
* Action must be taken immediately.
*
* Example: Entire website down, database unavailable, etc. This should
* trigger the SMS alerts and wake you up.
*
* @param string $message
* @param array $context
*
* @return void
*/
public function alert($message, array $context = array())
{
$this->log(LogLevel::ALERT, $message, $context);
}
/**
* Critical conditions.
*
* Example: Application component unavailable, unexpected exception.
*
* @param string $message
* @param array $context
*
* @return void
*/
public function critical($message, array $context = array())
{
$this->log(LogLevel::CRITICAL, $message, $context);
}
/**
* Runtime errors that do not require immediate action but should typically
* be logged and monitored.
*
* @param string $message
* @param array $context
*
* @return void
*/
public function error($message, array $context = array())
{
$this->log(LogLevel::ERROR, $message, $context);
}
/**
* Exceptional occurrences that are not errors.
*
* Example: Use of deprecated APIs, poor use of an API, undesirable things
* that are not necessarily wrong.
*
* @param string $message
* @param array $context
*
* @return void
*/
public function warning($message, array $context = array())
{
$this->log(LogLevel::WARNING, $message, $context);
}
/**
* Normal but significant events.
*
* @param string $message
* @param array $context
*
* @return void
*/
public function notice($message, array $context = array())
{
$this->log(LogLevel::NOTICE, $message, $context);
}
/**
* Interesting events.
*
* Example: User logs in, SQL logs.
*
* @param string $message
* @param array $context
*
* @return void
*/
public function info($message, array $context = array())
{
$this->log(LogLevel::INFO, $message, $context);
}
/**
* Detailed debug information.
*
* @param string $message
* @param array $context
*
* @return void
*/
public function debug($message, array $context = array())
{
$this->log(LogLevel::DEBUG, $message, $context);
}
/**
* Logs with an arbitrary level.
*
* @param mixed $level
* @param string $message
* @param array $context
*
* @return void
*/
abstract public function log($level, $message, array $context = array());
}
<?php
namespace Psr\Log;
/**
* Describes log levels.
*/
class LogLevel
{
const EMERGENCY = 'emergency';
const ALERT = 'alert';
const CRITICAL = 'critical';
const ERROR = 'error';
const WARNING = 'warning';
const NOTICE = 'notice';
const INFO = 'info';
const DEBUG = 'debug';
}
<?php
namespace Psr\Log;
/**
* This Logger can be used to avoid conditional log calls.
*
* Logging should always be optional, and if no logger is provided to your
* library creating a NullLogger instance to have something to throw logs at
* is a good way to avoid littering your code with `if ($this->logger) { }`
* blocks.
*/
class NullLogger extends AbstractLogger
{
/**
* Logs with an arbitrary level.
*
* @param mixed $level
* @param string $message
* @param array $context
*
* @return void
*/
public function log($level, $message, array $context = array())
{
// noop
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config;
use Symfony\Component\Config\Resource\SelfCheckingResourceChecker;
/**
* ConfigCache caches arbitrary content in files on disk.
*
* When in debug mode, those metadata resources that implement
* \Symfony\Component\Config\Resource\SelfCheckingResourceInterface will
* be used to check cache freshness.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Matthias Pigulla <mp@webfactory.de>
*/
class ConfigCache extends ResourceCheckerConfigCache
{
private $debug;
/**
* @param string $file The absolute cache path
* @param bool $debug Whether debugging is enabled or not
*/
public function __construct($file, $debug)
{
parent::__construct($file, array(
new SelfCheckingResourceChecker(),
));
$this->debug = (bool) $debug;
}
/**
* Checks if the cache is still fresh.
*
* This implementation always returns true when debug is off and the
* cache file exists.
*
* @return bool true if the cache is fresh, false otherwise
*/
public function isFresh()
{
if (!$this->debug && is_file($this->getPath())) {
return true;
}
return parent::isFresh();
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config;
/**
* Basic implementation of ConfigCacheFactoryInterface that
* creates an instance of the default ConfigCache.
*
* This factory and/or cache <em>do not</em> support cache validation
* by means of ResourceChecker instances (that is, service-based).
*
* @author Matthias Pigulla <mp@webfactory.de>
*/
class ConfigCacheFactory implements ConfigCacheFactoryInterface
{
/**
* @var bool Debug flag passed to the ConfigCache
*/
private $debug;
/**
* @param bool $debug The debug flag to pass to ConfigCache
*/
public function __construct($debug)
{
$this->debug = $debug;
}
/**
* {@inheritdoc}
*/
public function cache($file, $callback)
{
if (!is_callable($callback)) {
throw new \InvalidArgumentException(sprintf('Invalid type for callback argument. Expected callable, but got "%s".', gettype($callback)));
}
$cache = new ConfigCache($file, $this->debug);
if (!$cache->isFresh()) {
call_user_func($callback, $cache);
}
return $cache;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config;
/**
* Interface for a ConfigCache factory. This factory creates
* an instance of ConfigCacheInterface and initializes the
* cache if necessary.
*
* @author Matthias Pigulla <mp@webfactory.de>
*/
interface ConfigCacheFactoryInterface
{
/**
* Creates a cache instance and (re-)initializes it if necessary.
*
* @param string $file The absolute cache file path
* @param callable $callable The callable to be executed when the cache needs to be filled (i. e. is not fresh). The cache will be passed as the only parameter to this callback
*
* @return ConfigCacheInterface $configCache The cache instance
*/
public function cache($file, $callable);
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config;
use Symfony\Component\Config\Resource\ResourceInterface;
/**
* Interface for ConfigCache.
*
* @author Matthias Pigulla <mp@webfactory.de>
*/
interface ConfigCacheInterface
{
/**
* Gets the cache file path.
*
* @return string The cache file path
*/
public function getPath();
/**
* Checks if the cache is still fresh.
*
* This check should take the metadata passed to the write() method into consideration.
*
* @return bool Whether the cache is still fresh
*/
public function isFresh();
/**
* Writes the given content into the cache file. Metadata will be stored
* independently and can be used to check cache freshness at a later time.
*
* @param string $content The content to write into the cache
* @param ResourceInterface[]|null $metadata An array of ResourceInterface instances
*
* @throws \RuntimeException When the cache file cannot be written
*/
public function write($content, array $metadata = null);
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition;
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
use Symfony\Component\Config\Definition\Exception\InvalidTypeException;
use Symfony\Component\Config\Definition\Exception\UnsetKeyException;
/**
* Represents an Array node in the config tree.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class ArrayNode extends BaseNode implements PrototypeNodeInterface
{
protected $xmlRemappings = array();
protected $children = array();
protected $allowFalse = false;
protected $allowNewKeys = true;
protected $addIfNotSet = false;
protected $performDeepMerging = true;
protected $ignoreExtraKeys = false;
protected $removeExtraKeys = true;
protected $normalizeKeys = true;
public function setNormalizeKeys($normalizeKeys)
{
$this->normalizeKeys = (bool) $normalizeKeys;
}
/**
* Normalizes keys between the different configuration formats.
*
* Namely, you mostly have foo_bar in YAML while you have foo-bar in XML.
* After running this method, all keys are normalized to foo_bar.
*
* If you have a mixed key like foo-bar_moo, it will not be altered.
* The key will also not be altered if the target key already exists.
*
* @param mixed $value
*
* @return array The value with normalized keys
*/
protected function preNormalize($value)
{
if (!$this->normalizeKeys || !is_array($value)) {
return $value;
}
$normalized = array();
foreach ($value as $k => $v) {
if (false !== strpos($k, '-') && false === strpos($k, '_') && !array_key_exists($normalizedKey = str_replace('-', '_', $k), $value)) {
$normalized[$normalizedKey] = $v;
} else {
$normalized[$k] = $v;
}
}
return $normalized;
}
/**
* Retrieves the children of this node.
*
* @return array The children
*/
public function getChildren()
{
return $this->children;
}
/**
* Sets the xml remappings that should be performed.
*
* @param array $remappings an array of the form array(array(string, string))
*/
public function setXmlRemappings(array $remappings)
{
$this->xmlRemappings = $remappings;
}
/**
* Gets the xml remappings that should be performed.
*
* @return array $remappings an array of the form array(array(string, string))
*/
public function getXmlRemappings()
{
return $this->xmlRemappings;
}
/**
* Sets whether to add default values for this array if it has not been
* defined in any of the configuration files.
*
* @param bool $boolean
*/
public function setAddIfNotSet($boolean)
{
$this->addIfNotSet = (bool) $boolean;
}
/**
* Sets whether false is allowed as value indicating that the array should be unset.
*
* @param bool $allow
*/
public function setAllowFalse($allow)
{
$this->allowFalse = (bool) $allow;
}
/**
* Sets whether new keys can be defined in subsequent configurations.
*
* @param bool $allow
*/
public function setAllowNewKeys($allow)
{
$this->allowNewKeys = (bool) $allow;
}
/**
* Sets if deep merging should occur.
*
* @param bool $boolean
*/
public function setPerformDeepMerging($boolean)
{
$this->performDeepMerging = (bool) $boolean;
}
/**
* Whether extra keys should just be ignore without an exception.
*
* @param bool $boolean To allow extra keys
* @param bool $remove To remove extra keys
*/
public function setIgnoreExtraKeys($boolean, $remove = true)
{
$this->ignoreExtraKeys = (bool) $boolean;
$this->removeExtraKeys = $this->ignoreExtraKeys && $remove;
}
/**
* Sets the node Name.
*
* @param string $name The node's name
*/
public function setName($name)
{
$this->name = $name;
}
/**
* Checks if the node has a default value.
*
* @return bool
*/
public function hasDefaultValue()
{
return $this->addIfNotSet;
}
/**
* Retrieves the default value.
*
* @return array The default value
*
* @throws \RuntimeException if the node has no default value
*/
public function getDefaultValue()
{
if (!$this->hasDefaultValue()) {
throw new \RuntimeException(sprintf('The node at path "%s" has no default value.', $this->getPath()));
}
$defaults = array();
foreach ($this->children as $name => $child) {
if ($child->hasDefaultValue()) {
$defaults[$name] = $child->getDefaultValue();
}
}
return $defaults;
}
/**
* Adds a child node.
*
* @param NodeInterface $node The child node to add
*
* @throws \InvalidArgumentException when the child node has no name
* @throws \InvalidArgumentException when the child node's name is not unique
*/
public function addChild(NodeInterface $node)
{
$name = $node->getName();
if (!strlen($name)) {
throw new \InvalidArgumentException('Child nodes must be named.');
}
if (isset($this->children[$name])) {
throw new \InvalidArgumentException(sprintf('A child node named "%s" already exists.', $name));
}
$this->children[$name] = $node;
}
/**
* Finalizes the value of this node.
*
* @param mixed $value
*
* @return mixed The finalised value
*
* @throws UnsetKeyException
* @throws InvalidConfigurationException if the node doesn't have enough children
*/
protected function finalizeValue($value)
{
if (false === $value) {
$msg = sprintf('Unsetting key for path "%s", value: %s', $this->getPath(), json_encode($value));
throw new UnsetKeyException($msg);
}
foreach ($this->children as $name => $child) {
if (!array_key_exists($name, $value)) {
if ($child->isRequired()) {
$msg = sprintf('The child node "%s" at path "%s" must be configured.', $name, $this->getPath());
$ex = new InvalidConfigurationException($msg);
$ex->setPath($this->getPath());
throw $ex;
}
if ($child->hasDefaultValue()) {
$value[$name] = $child->getDefaultValue();
}
continue;
}
try {
$value[$name] = $child->finalize($value[$name]);
} catch (UnsetKeyException $e) {
unset($value[$name]);
}
}
return $value;
}
/**
* Validates the type of the value.
*
* @param mixed $value
*
* @throws InvalidTypeException
*/
protected function validateType($value)
{
if (!is_array($value) && (!$this->allowFalse || false !== $value)) {
$ex = new InvalidTypeException(sprintf(
'Invalid type for path "%s". Expected array, but got %s',
$this->getPath(),
gettype($value)
));
if ($hint = $this->getInfo()) {
$ex->addHint($hint);
}
$ex->setPath($this->getPath());
throw $ex;
}
}
/**
* Normalizes the value.
*
* @param mixed $value The value to normalize
*
* @return mixed The normalized value
*
* @throws InvalidConfigurationException
*/
protected function normalizeValue($value)
{
if (false === $value) {
return $value;
}
$value = $this->remapXml($value);
$normalized = array();
foreach ($value as $name => $val) {
if (isset($this->children[$name])) {
$normalized[$name] = $this->children[$name]->normalize($val);
unset($value[$name]);
} elseif (!$this->removeExtraKeys) {
$normalized[$name] = $val;
}
}
// if extra fields are present, throw exception
if (count($value) && !$this->ignoreExtraKeys) {
$msg = sprintf('Unrecognized option%s "%s" under "%s"', 1 === count($value) ? '' : 's', implode(', ', array_keys($value)), $this->getPath());
$ex = new InvalidConfigurationException($msg);
$ex->setPath($this->getPath());
throw $ex;
}
return $normalized;
}
/**
* Remaps multiple singular values to a single plural value.
*
* @param array $value The source values
*
* @return array The remapped values
*/
protected function remapXml($value)
{
foreach ($this->xmlRemappings as list($singular, $plural)) {
if (!isset($value[$singular])) {
continue;
}
$value[$plural] = Processor::normalizeConfig($value, $singular, $plural);
unset($value[$singular]);
}
return $value;
}
/**
* Merges values together.
*
* @param mixed $leftSide The left side to merge
* @param mixed $rightSide The right side to merge
*
* @return mixed The merged values
*
* @throws InvalidConfigurationException
* @throws \RuntimeException
*/
protected function mergeValues($leftSide, $rightSide)
{
if (false === $rightSide) {
// if this is still false after the last config has been merged the
// finalization pass will take care of removing this key entirely
return false;
}
if (false === $leftSide || !$this->performDeepMerging) {
return $rightSide;
}
foreach ($rightSide as $k => $v) {
// no conflict
if (!array_key_exists($k, $leftSide)) {
if (!$this->allowNewKeys) {
$ex = new InvalidConfigurationException(sprintf(
'You are not allowed to define new elements for path "%s". '
.'Please define all elements for this path in one config file. '
.'If you are trying to overwrite an element, make sure you redefine it '
.'with the same name.',
$this->getPath()
));
$ex->setPath($this->getPath());
throw $ex;
}
$leftSide[$k] = $v;
continue;
}
if (!isset($this->children[$k])) {
throw new \RuntimeException('merge() expects a normalized config array.');
}
$leftSide[$k] = $this->children[$k]->merge($leftSide[$k], $v);
}
return $leftSide;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition;
use Symfony\Component\Config\Definition\Exception\Exception;
use Symfony\Component\Config\Definition\Exception\ForbiddenOverwriteException;
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
use Symfony\Component\Config\Definition\Exception\InvalidTypeException;
/**
* The base node class.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
abstract class BaseNode implements NodeInterface
{
protected $name;
protected $parent;
protected $normalizationClosures = array();
protected $finalValidationClosures = array();
protected $allowOverwrite = true;
protected $required = false;
protected $equivalentValues = array();
protected $attributes = array();
/**
* Constructor.
*
* @param string $name The name of the node
* @param NodeInterface $parent The parent of this node
*
* @throws \InvalidArgumentException if the name contains a period.
*/
public function __construct($name, NodeInterface $parent = null)
{
if (false !== strpos($name, '.')) {
throw new \InvalidArgumentException('The name must not contain ".".');
}
$this->name = $name;
$this->parent = $parent;
}
public function setAttribute($key, $value)
{
$this->attributes[$key] = $value;
}
public function getAttribute($key, $default = null)
{
return isset($this->attributes[$key]) ? $this->attributes[$key] : $default;
}
public function hasAttribute($key)
{
return isset($this->attributes[$key]);
}
public function getAttributes()
{
return $this->attributes;
}
public function setAttributes(array $attributes)
{
$this->attributes = $attributes;
}
public function removeAttribute($key)
{
unset($this->attributes[$key]);
}
/**
* Sets an info message.
*
* @param string $info
*/
public function setInfo($info)
{
$this->setAttribute('info', $info);
}
/**
* Returns info message.
*
* @return string The info text
*/
public function getInfo()
{
return $this->getAttribute('info');
}
/**
* Sets the example configuration for this node.
*
* @param string|array $example
*/
public function setExample($example)
{
$this->setAttribute('example', $example);
}
/**
* Retrieves the example configuration for this node.
*
* @return string|array The example
*/
public function getExample()
{
return $this->getAttribute('example');
}
/**
* Adds an equivalent value.
*
* @param mixed $originalValue
* @param mixed $equivalentValue
*/
public function addEquivalentValue($originalValue, $equivalentValue)
{
$this->equivalentValues[] = array($originalValue, $equivalentValue);
}
/**
* Set this node as required.
*
* @param bool $boolean Required node
*/
public function setRequired($boolean)
{
$this->required = (bool) $boolean;
}
/**
* Sets if this node can be overridden.
*
* @param bool $allow
*/
public function setAllowOverwrite($allow)
{
$this->allowOverwrite = (bool) $allow;
}
/**
* Sets the closures used for normalization.
*
* @param \Closure[] $closures An array of Closures used for normalization
*/
public function setNormalizationClosures(array $closures)
{
$this->normalizationClosures = $closures;
}
/**
* Sets the closures used for final validation.
*
* @param \Closure[] $closures An array of Closures used for final validation
*/
public function setFinalValidationClosures(array $closures)
{
$this->finalValidationClosures = $closures;
}
/**
* Checks if this node is required.
*
* @return bool
*/
public function isRequired()
{
return $this->required;
}
/**
* Returns the name of this node.
*
* @return string The Node's name
*/
public function getName()
{
return $this->name;
}
/**
* Retrieves the path of this node.
*
* @return string The Node's path
*/
public function getPath()
{
$path = $this->name;
if (null !== $this->parent) {
$path = $this->parent->getPath().'.'.$path;
}
return $path;
}
/**
* Merges two values together.
*
* @param mixed $leftSide
* @param mixed $rightSide
*
* @return mixed The merged value
*
* @throws ForbiddenOverwriteException
*/
final public function merge($leftSide, $rightSide)
{
if (!$this->allowOverwrite) {
throw new ForbiddenOverwriteException(sprintf(
'Configuration path "%s" cannot be overwritten. You have to '
.'define all options for this path, and any of its sub-paths in '
.'one configuration section.',
$this->getPath()
));
}
$this->validateType($leftSide);
$this->validateType($rightSide);
return $this->mergeValues($leftSide, $rightSide);
}
/**
* Normalizes a value, applying all normalization closures.
*
* @param mixed $value Value to normalize
*
* @return mixed The normalized value
*/
final public function normalize($value)
{
$value = $this->preNormalize($value);
// run custom normalization closures
foreach ($this->normalizationClosures as $closure) {
$value = $closure($value);
}
// replace value with their equivalent
foreach ($this->equivalentValues as $data) {
if ($data[0] === $value) {
$value = $data[1];
}
}
// validate type
$this->validateType($value);
// normalize value
return $this->normalizeValue($value);
}
/**
* Normalizes the value before any other normalization is applied.
*
* @param $value
*
* @return $value The normalized array value
*/
protected function preNormalize($value)
{
return $value;
}
/**
* Returns parent node for this node.
*
* @return NodeInterface|null
*/
public function getParent()
{
return $this->parent;
}
/**
* Finalizes a value, applying all finalization closures.
*
* @param mixed $value The value to finalize
*
* @return mixed The finalized value
*
* @throws Exception
* @throws InvalidConfigurationException
*/
final public function finalize($value)
{
$this->validateType($value);
$value = $this->finalizeValue($value);
// Perform validation on the final value if a closure has been set.
// The closure is also allowed to return another value.
foreach ($this->finalValidationClosures as $closure) {
try {
$value = $closure($value);
} catch (Exception $e) {
throw $e;
} catch (\Exception $e) {
throw new InvalidConfigurationException(sprintf('Invalid configuration for path "%s": %s', $this->getPath(), $e->getMessage()), $e->getCode(), $e);
}
}
return $value;
}
/**
* Validates the type of a Node.
*
* @param mixed $value The value to validate
*
* @throws InvalidTypeException when the value is invalid
*/
abstract protected function validateType($value);
/**
* Normalizes the value.
*
* @param mixed $value The value to normalize
*
* @return mixed The normalized value
*/
abstract protected function normalizeValue($value);
/**
* Merges two values together.
*
* @param mixed $leftSide
* @param mixed $rightSide
*
* @return mixed The merged value
*/
abstract protected function mergeValues($leftSide, $rightSide);
/**
* Finalizes a value.
*
* @param mixed $value The value to finalize
*
* @return mixed The finalized value
*/
abstract protected function finalizeValue($value);
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition;
use Symfony\Component\Config\Definition\Exception\InvalidTypeException;
/**
* This node represents a Boolean value in the config tree.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class BooleanNode extends ScalarNode
{
/**
* {@inheritdoc}
*/
protected function validateType($value)
{
if (!is_bool($value)) {
$ex = new InvalidTypeException(sprintf(
'Invalid type for path "%s". Expected boolean, but got %s.',
$this->getPath(),
gettype($value)
));
if ($hint = $this->getInfo()) {
$ex->addHint($hint);
}
$ex->setPath($this->getPath());
throw $ex;
}
}
/**
* {@inheritdoc}
*/
protected function isValueEmpty($value)
{
// a boolean value cannot be empty
return false;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition\Builder;
use Symfony\Component\Config\Definition\ArrayNode;
use Symfony\Component\Config\Definition\PrototypedArrayNode;
use Symfony\Component\Config\Definition\Exception\InvalidDefinitionException;
/**
* This class provides a fluent interface for defining an array node.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class ArrayNodeDefinition extends NodeDefinition implements ParentNodeDefinitionInterface
{
protected $performDeepMerging = true;
protected $ignoreExtraKeys = false;
protected $removeExtraKeys = true;
protected $children = array();
protected $prototype;
protected $atLeastOne = false;
protected $allowNewKeys = true;
protected $key;
protected $removeKeyItem;
protected $addDefaults = false;
protected $addDefaultChildren = false;
protected $nodeBuilder;
protected $normalizeKeys = true;
/**
* {@inheritdoc}
*/
public function __construct($name, NodeParentInterface $parent = null)
{
parent::__construct($name, $parent);
$this->nullEquivalent = array();
$this->trueEquivalent = array();
}
/**
* Sets a custom children builder.
*
* @param NodeBuilder $builder A custom NodeBuilder
*/
public function setBuilder(NodeBuilder $builder)
{
$this->nodeBuilder = $builder;
}
/**
* Returns a builder to add children nodes.
*
* @return NodeBuilder
*/
public function children()
{
return $this->getNodeBuilder();
}
/**
* Sets a prototype for child nodes.
*
* @param string $type the type of node
*
* @return NodeDefinition
*/
public function prototype($type)
{
return $this->prototype = $this->getNodeBuilder()->node(null, $type)->setParent($this);
}
/**
* Adds the default value if the node is not set in the configuration.
*
* This method is applicable to concrete nodes only (not to prototype nodes).
* If this function has been called and the node is not set during the finalization
* phase, it's default value will be derived from its children default values.
*
* @return ArrayNodeDefinition
*/
public function addDefaultsIfNotSet()
{
$this->addDefaults = true;
return $this;
}
/**
* Adds children with a default value when none are defined.
*
* @param int|string|array|null $children The number of children|The child name|The children names to be added
*
* This method is applicable to prototype nodes only.
*
* @return ArrayNodeDefinition
*/
public function addDefaultChildrenIfNoneSet($children = null)
{
$this->addDefaultChildren = $children;
return $this;
}
/**
* Requires the node to have at least one element.
*
* This method is applicable to prototype nodes only.
*
* @return ArrayNodeDefinition
*/
public function requiresAtLeastOneElement()
{
$this->atLeastOne = true;
return $this;
}
/**
* Disallows adding news keys in a subsequent configuration.
*
* If used all keys have to be defined in the same configuration file.
*
* @return ArrayNodeDefinition
*/
public function disallowNewKeysInSubsequentConfigs()
{
$this->allowNewKeys = false;
return $this;
}
/**
* Sets a normalization rule for XML configurations.
*
* @param string $singular The key to remap
* @param string $plural The plural of the key for irregular plurals
*
* @return ArrayNodeDefinition
*/
public function fixXmlConfig($singular, $plural = null)
{
$this->normalization()->remap($singular, $plural);
return $this;
}
/**
* Sets the attribute which value is to be used as key.
*
* This is useful when you have an indexed array that should be an
* associative array. You can select an item from within the array
* to be the key of the particular item. For example, if "id" is the
* "key", then:
*
* array(
* array('id' => 'my_name', 'foo' => 'bar'),
* );
*
* becomes
*
* array(
* 'my_name' => array('foo' => 'bar'),
* );
*
* If you'd like "'id' => 'my_name'" to still be present in the resulting
* array, then you can set the second argument of this method to false.
*
* This method is applicable to prototype nodes only.
*
* @param string $name The name of the key
* @param bool $removeKeyItem Whether or not the key item should be removed
*
* @return ArrayNodeDefinition
*/
public function useAttributeAsKey($name, $removeKeyItem = true)
{
$this->key = $name;
$this->removeKeyItem = $removeKeyItem;
return $this;
}
/**
* Sets whether the node can be unset.
*
* @param bool $allow
*
* @return ArrayNodeDefinition
*/
public function canBeUnset($allow = true)
{
$this->merge()->allowUnset($allow);
return $this;
}
/**
* Adds an "enabled" boolean to enable the current section.
*
* By default, the section is disabled. If any configuration is specified then
* the node will be automatically enabled:
*
* enableableArrayNode: {enabled: true, ...} # The config is enabled & default values get overridden
* enableableArrayNode: ~ # The config is enabled & use the default values
* enableableArrayNode: true # The config is enabled & use the default values
* enableableArrayNode: {other: value, ...} # The config is enabled & default values get overridden
* enableableArrayNode: {enabled: false, ...} # The config is disabled
* enableableArrayNode: false # The config is disabled
*
* @return ArrayNodeDefinition
*/
public function canBeEnabled()
{
$this
->addDefaultsIfNotSet()
->treatFalseLike(array('enabled' => false))
->treatTrueLike(array('enabled' => true))
->treatNullLike(array('enabled' => true))
->beforeNormalization()
->ifArray()
->then(function ($v) {
$v['enabled'] = isset($v['enabled']) ? $v['enabled'] : true;
return $v;
})
->end()
->children()
->booleanNode('enabled')
->defaultFalse()
;
return $this;
}
/**
* Adds an "enabled" boolean to enable the current section.
*
* By default, the section is enabled.
*
* @return ArrayNodeDefinition
*/
public function canBeDisabled()
{
$this
->addDefaultsIfNotSet()
->treatFalseLike(array('enabled' => false))
->treatTrueLike(array('enabled' => true))
->treatNullLike(array('enabled' => true))
->children()
->booleanNode('enabled')
->defaultTrue()
;
return $this;
}
/**
* Disables the deep merging of the node.
*
* @return ArrayNodeDefinition
*/
public function performNoDeepMerging()
{
$this->performDeepMerging = false;
return $this;
}
/**
* Allows extra config keys to be specified under an array without
* throwing an exception.
*
* Those config values are simply ignored and removed from the
* resulting array. This should be used only in special cases where
* you want to send an entire configuration array through a special
* tree that processes only part of the array.
*
* @param bool $remove Whether to remove the extra keys
*
* @return ArrayNodeDefinition
*/
public function ignoreExtraKeys($remove = true)
{
$this->ignoreExtraKeys = true;
$this->removeExtraKeys = $remove;
return $this;
}
/**
* Sets key normalization.
*
* @param bool $bool Whether to enable key normalization
*
* @return ArrayNodeDefinition
*/
public function normalizeKeys($bool)
{
$this->normalizeKeys = (bool) $bool;
return $this;
}
/**
* Appends a node definition.
*
* $node = new ArrayNodeDefinition()
* ->children()
* ->scalarNode('foo')->end()
* ->scalarNode('baz')->end()
* ->end()
* ->append($this->getBarNodeDefinition())
* ;
*
* @param NodeDefinition $node A NodeDefinition instance
*
* @return ArrayNodeDefinition This node
*/
public function append(NodeDefinition $node)
{
$this->children[$node->name] = $node->setParent($this);
return $this;
}
/**
* Returns a node builder to be used to add children and prototype.
*
* @return NodeBuilder The node builder
*/
protected function getNodeBuilder()
{
if (null === $this->nodeBuilder) {
$this->nodeBuilder = new NodeBuilder();
}
return $this->nodeBuilder->setParent($this);
}
/**
* {@inheritdoc}
*/
protected function createNode()
{
if (null === $this->prototype) {
$node = new ArrayNode($this->name, $this->parent);
$this->validateConcreteNode($node);
$node->setAddIfNotSet($this->addDefaults);
foreach ($this->children as $child) {
$child->parent = $node;
$node->addChild($child->getNode());
}
} else {
$node = new PrototypedArrayNode($this->name, $this->parent);
$this->validatePrototypeNode($node);
if (null !== $this->key) {
$node->setKeyAttribute($this->key, $this->removeKeyItem);
}
if (true === $this->atLeastOne) {
$node->setMinNumberOfElements(1);
}
if ($this->default) {
$node->setDefaultValue($this->defaultValue);
}
if (false !== $this->addDefaultChildren) {
$node->setAddChildrenIfNoneSet($this->addDefaultChildren);
if ($this->prototype instanceof static && null === $this->prototype->prototype) {
$this->prototype->addDefaultsIfNotSet();
}
}
$this->prototype->parent = $node;
$node->setPrototype($this->prototype->getNode());
}
$node->setAllowNewKeys($this->allowNewKeys);
$node->addEquivalentValue(null, $this->nullEquivalent);
$node->addEquivalentValue(true, $this->trueEquivalent);
$node->addEquivalentValue(false, $this->falseEquivalent);
$node->setPerformDeepMerging($this->performDeepMerging);
$node->setRequired($this->required);
$node->setIgnoreExtraKeys($this->ignoreExtraKeys, $this->removeExtraKeys);
$node->setNormalizeKeys($this->normalizeKeys);
if (null !== $this->normalization) {
$node->setNormalizationClosures($this->normalization->before);
$node->setXmlRemappings($this->normalization->remappings);
}
if (null !== $this->merge) {
$node->setAllowOverwrite($this->merge->allowOverwrite);
$node->setAllowFalse($this->merge->allowFalse);
}
if (null !== $this->validation) {
$node->setFinalValidationClosures($this->validation->rules);
}
return $node;
}
/**
* Validate the configuration of a concrete node.
*
* @param ArrayNode $node The related node
*
* @throws InvalidDefinitionException
*/
protected function validateConcreteNode(ArrayNode $node)
{
$path = $node->getPath();
if (null !== $this->key) {
throw new InvalidDefinitionException(
sprintf('->useAttributeAsKey() is not applicable to concrete nodes at path "%s"', $path)
);
}
if (true === $this->atLeastOne) {
throw new InvalidDefinitionException(
sprintf('->requiresAtLeastOneElement() is not applicable to concrete nodes at path "%s"', $path)
);
}
if ($this->default) {
throw new InvalidDefinitionException(
sprintf('->defaultValue() is not applicable to concrete nodes at path "%s"', $path)
);
}
if (false !== $this->addDefaultChildren) {
throw new InvalidDefinitionException(
sprintf('->addDefaultChildrenIfNoneSet() is not applicable to concrete nodes at path "%s"', $path)
);
}
}
/**
* Validate the configuration of a prototype node.
*
* @param PrototypedArrayNode $node The related node
*
* @throws InvalidDefinitionException
*/
protected function validatePrototypeNode(PrototypedArrayNode $node)
{
$path = $node->getPath();
if ($this->addDefaults) {
throw new InvalidDefinitionException(
sprintf('->addDefaultsIfNotSet() is not applicable to prototype nodes at path "%s"', $path)
);
}
if (false !== $this->addDefaultChildren) {
if ($this->default) {
throw new InvalidDefinitionException(
sprintf('A default value and default children might not be used together at path "%s"', $path)
);
}
if (null !== $this->key && (null === $this->addDefaultChildren || is_int($this->addDefaultChildren) && $this->addDefaultChildren > 0)) {
throw new InvalidDefinitionException(
sprintf('->addDefaultChildrenIfNoneSet() should set default children names as ->useAttributeAsKey() is used at path "%s"', $path)
);
}
if (null === $this->key && (is_string($this->addDefaultChildren) || is_array($this->addDefaultChildren))) {
throw new InvalidDefinitionException(
sprintf('->addDefaultChildrenIfNoneSet() might not set default children names as ->useAttributeAsKey() is not used at path "%s"', $path)
);
}
}
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition\Builder;
use Symfony\Component\Config\Definition\BooleanNode;
use Symfony\Component\Config\Definition\Exception\InvalidDefinitionException;
/**
* This class provides a fluent interface for defining a node.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class BooleanNodeDefinition extends ScalarNodeDefinition
{
/**
* {@inheritdoc}
*/
public function __construct($name, NodeParentInterface $parent = null)
{
parent::__construct($name, $parent);
$this->nullEquivalent = true;
}
/**
* Instantiate a Node.
*
* @return BooleanNode The node
*/
protected function instantiateNode()
{
return new BooleanNode($this->name, $this->parent);
}
/**
* {@inheritdoc}
*
* @throws InvalidDefinitionException
*/
public function cannotBeEmpty()
{
throw new InvalidDefinitionException('->cannotBeEmpty() is not applicable to BooleanNodeDefinition.');
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition\Builder;
use Symfony\Component\Config\Definition\EnumNode;
/**
* Enum Node Definition.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class EnumNodeDefinition extends ScalarNodeDefinition
{
private $values;
/**
* @param array $values
*
* @return EnumNodeDefinition|$this
*/
public function values(array $values)
{
$values = array_unique($values);
if (empty($values)) {
throw new \InvalidArgumentException('->values() must be called with at least one value.');
}
$this->values = $values;
return $this;
}
/**
* Instantiate a Node.
*
* @return EnumNode The node
*
* @throws \RuntimeException
*/
protected function instantiateNode()
{
if (null === $this->values) {
throw new \RuntimeException('You must call ->values() on enum nodes.');
}
return new EnumNode($this->name, $this->parent, $this->values);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition\Builder;
use Symfony\Component\Config\Definition\Exception\UnsetKeyException;
/**
* This class builds an if expression.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
* @author Christophe Coevoet <stof@notk.org>
*/
class ExprBuilder
{
protected $node;
public $ifPart;
public $thenPart;
/**
* Constructor.
*
* @param NodeDefinition $node The related node
*/
public function __construct(NodeDefinition $node)
{
$this->node = $node;
}
/**
* Marks the expression as being always used.
*
* @param \Closure $then
*
* @return ExprBuilder
*/
public function always(\Closure $then = null)
{
$this->ifPart = function ($v) { return true; };
if (null !== $then) {
$this->thenPart = $then;
}
return $this;
}
/**
* Sets a closure to use as tests.
*
* The default one tests if the value is true.
*
* @param \Closure $closure
*
* @return ExprBuilder
*/
public function ifTrue(\Closure $closure = null)
{
if (null === $closure) {
$closure = function ($v) { return true === $v; };
}
$this->ifPart = $closure;
return $this;
}
/**
* Tests if the value is a string.
*
* @return ExprBuilder
*/
public function ifString()
{
$this->ifPart = function ($v) { return is_string($v); };
return $this;
}
/**
* Tests if the value is null.
*
* @return ExprBuilder
*/
public function ifNull()
{
$this->ifPart = function ($v) { return null === $v; };
return $this;
}
/**
* Tests if the value is an array.
*
* @return ExprBuilder
*/
public function ifArray()
{
$this->ifPart = function ($v) { return is_array($v); };
return $this;
}
/**
* Tests if the value is in an array.
*
* @param array $array
*
* @return ExprBuilder
*/
public function ifInArray(array $array)
{
$this->ifPart = function ($v) use ($array) { return in_array($v, $array, true); };
return $this;
}
/**
* Tests if the value is not in an array.
*
* @param array $array
*
* @return ExprBuilder
*/
public function ifNotInArray(array $array)
{
$this->ifPart = function ($v) use ($array) { return !in_array($v, $array, true); };
return $this;
}
/**
* Sets the closure to run if the test pass.
*
* @param \Closure $closure
*
* @return ExprBuilder
*/
public function then(\Closure $closure)
{
$this->thenPart = $closure;
return $this;
}
/**
* Sets a closure returning an empty array.
*
* @return ExprBuilder
*/
public function thenEmptyArray()
{
$this->thenPart = function ($v) { return array(); };
return $this;
}
/**
* Sets a closure marking the value as invalid at validation time.
*
* if you want to add the value of the node in your message just use a %s placeholder.
*
* @param string $message
*
* @return ExprBuilder
*
* @throws \InvalidArgumentException
*/
public function thenInvalid($message)
{
$this->thenPart = function ($v) use ($message) {throw new \InvalidArgumentException(sprintf($message, json_encode($v))); };
return $this;
}
/**
* Sets a closure unsetting this key of the array at validation time.
*
* @return ExprBuilder
*
* @throws UnsetKeyException
*/
public function thenUnset()
{
$this->thenPart = function ($v) { throw new UnsetKeyException('Unsetting key'); };
return $this;
}
/**
* Returns the related node.
*
* @return NodeDefinition
*
* @throws \RuntimeException
*/
public function end()
{
if (null === $this->ifPart) {
throw new \RuntimeException('You must specify an if part.');
}
if (null === $this->thenPart) {
throw new \RuntimeException('You must specify a then part.');
}
return $this->node;
}
/**
* Builds the expressions.
*
* @param ExprBuilder[] $expressions An array of ExprBuilder instances to build
*
* @return array
*/
public static function buildExpressions(array $expressions)
{
foreach ($expressions as $k => $expr) {
if ($expr instanceof self) {
$if = $expr->ifPart;
$then = $expr->thenPart;
$expressions[$k] = function ($v) use ($if, $then) {
return $if($v) ? $then($v) : $v;
};
}
}
return $expressions;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition\Builder;
use Symfony\Component\Config\Definition\FloatNode;
/**
* This class provides a fluent interface for defining a float node.
*
* @author Jeanmonod David <david.jeanmonod@gmail.com>
*/
class FloatNodeDefinition extends NumericNodeDefinition
{
/**
* Instantiates a Node.
*
* @return FloatNode The node
*/
protected function instantiateNode()
{
return new FloatNode($this->name, $this->parent, $this->min, $this->max);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition\Builder;
use Symfony\Component\Config\Definition\IntegerNode;
/**
* This class provides a fluent interface for defining an integer node.
*
* @author Jeanmonod David <david.jeanmonod@gmail.com>
*/
class IntegerNodeDefinition extends NumericNodeDefinition
{
/**
* Instantiates a Node.
*
* @return IntegerNode The node
*/
protected function instantiateNode()
{
return new IntegerNode($this->name, $this->parent, $this->min, $this->max);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition\Builder;
/**
* This class builds merge conditions.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class MergeBuilder
{
protected $node;
public $allowFalse = false;
public $allowOverwrite = true;
/**
* Constructor.
*
* @param NodeDefinition $node The related node
*/
public function __construct(NodeDefinition $node)
{
$this->node = $node;
}
/**
* Sets whether the node can be unset.
*
* @param bool $allow
*
* @return MergeBuilder
*/
public function allowUnset($allow = true)
{
$this->allowFalse = $allow;
return $this;
}
/**
* Sets whether the node can be overwritten.
*
* @param bool $deny Whether the overwriting is forbidden or not
*
* @return MergeBuilder
*/
public function denyOverwrite($deny = true)
{
$this->allowOverwrite = !$deny;
return $this;
}
/**
* Returns the related node.
*
* @return NodeDefinition
*/
public function end()
{
return $this->node;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition\Builder;
/**
* This class provides a fluent interface for building a node.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class NodeBuilder implements NodeParentInterface
{
protected $parent;
protected $nodeMapping;
/**
* Constructor.
*/
public function __construct()
{
$this->nodeMapping = array(
'variable' => __NAMESPACE__.'\\VariableNodeDefinition',
'scalar' => __NAMESPACE__.'\\ScalarNodeDefinition',
'boolean' => __NAMESPACE__.'\\BooleanNodeDefinition',
'integer' => __NAMESPACE__.'\\IntegerNodeDefinition',
'float' => __NAMESPACE__.'\\FloatNodeDefinition',
'array' => __NAMESPACE__.'\\ArrayNodeDefinition',
'enum' => __NAMESPACE__.'\\EnumNodeDefinition',
);
}
/**
* Set the parent node.
*
* @param ParentNodeDefinitionInterface $parent The parent node
*
* @return NodeBuilder This node builder
*/
public function setParent(ParentNodeDefinitionInterface $parent = null)
{
$this->parent = $parent;
return $this;
}
/**
* Creates a child array node.
*
* @param string $name The name of the node
*
* @return ArrayNodeDefinition The child node
*/
public function arrayNode($name)
{
return $this->node($name, 'array');
}
/**
* Creates a child scalar node.
*
* @param string $name the name of the node
*
* @return ScalarNodeDefinition The child node
*/
public function scalarNode($name)
{
return $this->node($name, 'scalar');
}
/**
* Creates a child Boolean node.
*
* @param string $name The name of the node
*
* @return BooleanNodeDefinition The child node
*/
public function booleanNode($name)
{
return $this->node($name, 'boolean');
}
/**
* Creates a child integer node.
*
* @param string $name the name of the node
*
* @return IntegerNodeDefinition The child node
*/
public function integerNode($name)
{
return $this->node($name, 'integer');
}
/**
* Creates a child float node.
*
* @param string $name the name of the node
*
* @return FloatNodeDefinition The child node
*/
public function floatNode($name)
{
return $this->node($name, 'float');
}
/**
* Creates a child EnumNode.
*
* @param string $name
*
* @return EnumNodeDefinition
*/
public function enumNode($name)
{
return $this->node($name, 'enum');
}
/**
* Creates a child variable node.
*
* @param string $name The name of the node
*
* @return VariableNodeDefinition The builder of the child node
*/
public function variableNode($name)
{
return $this->node($name, 'variable');
}
/**
* Returns the parent node.
*
* @return ParentNodeDefinitionInterface|NodeDefinition The parent node
*/
public function end()
{
return $this->parent;
}
/**
* Creates a child node.
*
* @param string $name The name of the node
* @param string $type The type of the node
*
* @return NodeDefinition The child node
*
* @throws \RuntimeException When the node type is not registered
* @throws \RuntimeException When the node class is not found
*/
public function node($name, $type)
{
$class = $this->getNodeClass($type);
$node = new $class($name);
$this->append($node);
return $node;
}
/**
* Appends a node definition.
*
* Usage:
*
* $node = new ArrayNodeDefinition('name')
* ->children()
* ->scalarNode('foo')->end()
* ->scalarNode('baz')->end()
* ->append($this->getBarNodeDefinition())
* ->end()
* ;
*
* @param NodeDefinition $node
*
* @return NodeBuilder This node builder
*/
public function append(NodeDefinition $node)
{
if ($node instanceof ParentNodeDefinitionInterface) {
$builder = clone $this;
$builder->setParent(null);
$node->setBuilder($builder);
}
if (null !== $this->parent) {
$this->parent->append($node);
// Make this builder the node parent to allow for a fluid interface
$node->setParent($this);
}
return $this;
}
/**
* Adds or overrides a node Type.
*
* @param string $type The name of the type
* @param string $class The fully qualified name the node definition class
*
* @return NodeBuilder This node builder
*/
public function setNodeClass($type, $class)
{
$this->nodeMapping[strtolower($type)] = $class;
return $this;
}
/**
* Returns the class name of the node definition.
*
* @param string $type The node type
*
* @return string The node definition class name
*
* @throws \RuntimeException When the node type is not registered
* @throws \RuntimeException When the node class is not found
*/
protected function getNodeClass($type)
{
$type = strtolower($type);
if (!isset($this->nodeMapping[$type])) {
throw new \RuntimeException(sprintf('The node type "%s" is not registered.', $type));
}
$class = $this->nodeMapping[$type];
if (!class_exists($class)) {
throw new \RuntimeException(sprintf('The node class "%s" does not exist.', $class));
}
return $class;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition\Builder;
use Symfony\Component\Config\Definition\NodeInterface;
use Symfony\Component\Config\Definition\Exception\InvalidDefinitionException;
/**
* This class provides a fluent interface for defining a node.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
abstract class NodeDefinition implements NodeParentInterface
{
protected $name;
protected $normalization;
protected $validation;
protected $defaultValue;
protected $default = false;
protected $required = false;
protected $merge;
protected $allowEmptyValue = true;
protected $nullEquivalent;
protected $trueEquivalent = true;
protected $falseEquivalent = false;
/**
* @var NodeParentInterface|null
*/
protected $parent;
protected $attributes = array();
/**
* Constructor.
*
* @param string $name The name of the node
* @param NodeParentInterface|null $parent The parent
*/
public function __construct($name, NodeParentInterface $parent = null)
{
$this->parent = $parent;
$this->name = $name;
}
/**
* Sets the parent node.
*
* @param NodeParentInterface $parent The parent
*
* @return NodeDefinition|$this
*/
public function setParent(NodeParentInterface $parent)
{
$this->parent = $parent;
return $this;
}
/**
* Sets info message.
*
* @param string $info The info text
*
* @return NodeDefinition|$this
*/
public function info($info)
{
return $this->attribute('info', $info);
}
/**
* Sets example configuration.
*
* @param string|array $example
*
* @return NodeDefinition|$this
*/
public function example($example)
{
return $this->attribute('example', $example);
}
/**
* Sets an attribute on the node.
*
* @param string $key
* @param mixed $value
*
* @return NodeDefinition|$this
*/
public function attribute($key, $value)
{
$this->attributes[$key] = $value;
return $this;
}
/**
* Returns the parent node.
*
* @return NodeParentInterface|NodeBuilder|NodeDefinition|null The builder of the parent node
*/
public function end()
{
return $this->parent;
}
/**
* Creates the node.
*
* @param bool $forceRootNode Whether to force this node as the root node
*
* @return NodeInterface
*/
public function getNode($forceRootNode = false)
{
if ($forceRootNode) {
$this->parent = null;
}
if (null !== $this->normalization) {
$this->normalization->before = ExprBuilder::buildExpressions($this->normalization->before);
}
if (null !== $this->validation) {
$this->validation->rules = ExprBuilder::buildExpressions($this->validation->rules);
}
$node = $this->createNode();
$node->setAttributes($this->attributes);
return $node;
}
/**
* Sets the default value.
*
* @param mixed $value The default value
*
* @return NodeDefinition|$this
*/
public function defaultValue($value)
{
$this->default = true;
$this->defaultValue = $value;
return $this;
}
/**
* Sets the node as required.
*
* @return NodeDefinition|$this
*/
public function isRequired()
{
$this->required = true;
return $this;
}
/**
* Sets the equivalent value used when the node contains null.
*
* @param mixed $value
*
* @return NodeDefinition|$this
*/
public function treatNullLike($value)
{
$this->nullEquivalent = $value;
return $this;
}
/**
* Sets the equivalent value used when the node contains true.
*
* @param mixed $value
*
* @return NodeDefinition|$this
*/
public function treatTrueLike($value)
{
$this->trueEquivalent = $value;
return $this;
}
/**
* Sets the equivalent value used when the node contains false.
*
* @param mixed $value
*
* @return NodeDefinition|$this
*/
public function treatFalseLike($value)
{
$this->falseEquivalent = $value;
return $this;
}
/**
* Sets null as the default value.
*
* @return NodeDefinition|$this
*/
public function defaultNull()
{
return $this->defaultValue(null);
}
/**
* Sets true as the default value.
*
* @return NodeDefinition|$this
*/
public function defaultTrue()
{
return $this->defaultValue(true);
}
/**
* Sets false as the default value.
*
* @return NodeDefinition|$this
*/
public function defaultFalse()
{
return $this->defaultValue(false);
}
/**
* Sets an expression to run before the normalization.
*
* @return ExprBuilder
*/
public function beforeNormalization()
{
return $this->normalization()->before();
}
/**
* Denies the node value being empty.
*
* @return NodeDefinition|$this
*/
public function cannotBeEmpty()
{
$this->allowEmptyValue = false;
return $this;
}
/**
* Sets an expression to run for the validation.
*
* The expression receives the value of the node and must return it. It can
* modify it.
* An exception should be thrown when the node is not valid.
*
* @return ExprBuilder
*/
public function validate()
{
return $this->validation()->rule();
}
/**
* Sets whether the node can be overwritten.
*
* @param bool $deny Whether the overwriting is forbidden or not
*
* @return NodeDefinition|$this
*/
public function cannotBeOverwritten($deny = true)
{
$this->merge()->denyOverwrite($deny);
return $this;
}
/**
* Gets the builder for validation rules.
*
* @return ValidationBuilder
*/
protected function validation()
{
if (null === $this->validation) {
$this->validation = new ValidationBuilder($this);
}
return $this->validation;
}
/**
* Gets the builder for merging rules.
*
* @return MergeBuilder
*/
protected function merge()
{
if (null === $this->merge) {
$this->merge = new MergeBuilder($this);
}
return $this->merge;
}
/**
* Gets the builder for normalization rules.
*
* @return NormalizationBuilder
*/
protected function normalization()
{
if (null === $this->normalization) {
$this->normalization = new NormalizationBuilder($this);
}
return $this->normalization;
}
/**
* Instantiate and configure the node according to this definition.
*
* @return NodeInterface $node The node instance
*
* @throws InvalidDefinitionException When the definition is invalid
*/
abstract protected function createNode();
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition\Builder;
/**
* An interface that must be implemented by all node parents.
*
* @author Victor Berchet <victor@suumit.com>
*/
interface NodeParentInterface
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition\Builder;
/**
* This class builds normalization conditions.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class NormalizationBuilder
{
protected $node;
public $before = array();
public $remappings = array();
/**
* Constructor.
*
* @param NodeDefinition $node The related node
*/
public function __construct(NodeDefinition $node)
{
$this->node = $node;
}
/**
* Registers a key to remap to its plural form.
*
* @param string $key The key to remap
* @param string $plural The plural of the key in case of irregular plural
*
* @return NormalizationBuilder
*/
public function remap($key, $plural = null)
{
$this->remappings[] = array($key, null === $plural ? $key.'s' : $plural);
return $this;
}
/**
* Registers a closure to run before the normalization or an expression builder to build it if null is provided.
*
* @param \Closure $closure
*
* @return ExprBuilder|NormalizationBuilder
*/
public function before(\Closure $closure = null)
{
if (null !== $closure) {
$this->before[] = $closure;
return $this;
}
return $this->before[] = new ExprBuilder($this->node);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition\Builder;
use Symfony\Component\Config\Definition\Exception\InvalidDefinitionException;
/**
* Abstract class that contains common code of integer and float node definitions.
*
* @author David Jeanmonod <david.jeanmonod@gmail.com>
*/
abstract class NumericNodeDefinition extends ScalarNodeDefinition
{
protected $min;
protected $max;
/**
* Ensures that the value is smaller than the given reference.
*
* @param mixed $max
*
* @return NumericNodeDefinition
*
* @throws \InvalidArgumentException when the constraint is inconsistent
*/
public function max($max)
{
if (isset($this->min) && $this->min > $max) {
throw new \InvalidArgumentException(sprintf('You cannot define a max(%s) as you already have a min(%s)', $max, $this->min));
}
$this->max = $max;
return $this;
}
/**
* Ensures that the value is bigger than the given reference.
*
* @param mixed $min
*
* @return NumericNodeDefinition
*
* @throws \InvalidArgumentException when the constraint is inconsistent
*/
public function min($min)
{
if (isset($this->max) && $this->max < $min) {
throw new \InvalidArgumentException(sprintf('You cannot define a min(%s) as you already have a max(%s)', $min, $this->max));
}
$this->min = $min;
return $this;
}
/**
* {@inheritdoc}
*
* @throws InvalidDefinitionException
*/
public function cannotBeEmpty()
{
throw new InvalidDefinitionException('->cannotBeEmpty() is not applicable to NumericNodeDefinition.');
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition\Builder;
/**
* An interface that must be implemented by nodes which can have children.
*
* @author Victor Berchet <victor@suumit.com>
*/
interface ParentNodeDefinitionInterface
{
public function children();
public function append(NodeDefinition $node);
public function setBuilder(NodeBuilder $builder);
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition\Builder;
use Symfony\Component\Config\Definition\ScalarNode;
/**
* This class provides a fluent interface for defining a node.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class ScalarNodeDefinition extends VariableNodeDefinition
{
/**
* Instantiate a Node.
*
* @return ScalarNode The node
*/
protected function instantiateNode()
{
return new ScalarNode($this->name, $this->parent);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition\Builder;
use Symfony\Component\Config\Definition\NodeInterface;
/**
* This is the entry class for building a config tree.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class TreeBuilder implements NodeParentInterface
{
protected $tree;
protected $root;
protected $builder;
/**
* Creates the root node.
*
* @param string $name The name of the root node
* @param string $type The type of the root node
* @param NodeBuilder $builder A custom node builder instance
*
* @return ArrayNodeDefinition|NodeDefinition The root node (as an ArrayNodeDefinition when the type is 'array')
*
* @throws \RuntimeException When the node type is not supported
*/
public function root($name, $type = 'array', NodeBuilder $builder = null)
{
$builder = $builder ?: new NodeBuilder();
return $this->root = $builder->node($name, $type)->setParent($this);
}
/**
* Builds the tree.
*
* @return NodeInterface
*
* @throws \RuntimeException
*/
public function buildTree()
{
if (null === $this->root) {
throw new \RuntimeException('The configuration tree has no root node.');
}
if (null !== $this->tree) {
return $this->tree;
}
return $this->tree = $this->root->getNode(true);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition\Builder;
/**
* This class builds validation conditions.
*
* @author Christophe Coevoet <stof@notk.org>
*/
class ValidationBuilder
{
protected $node;
public $rules = array();
/**
* Constructor.
*
* @param NodeDefinition $node The related node
*/
public function __construct(NodeDefinition $node)
{
$this->node = $node;
}
/**
* Registers a closure to run as normalization or an expression builder to build it if null is provided.
*
* @param \Closure $closure
*
* @return ExprBuilder|ValidationBuilder
*/
public function rule(\Closure $closure = null)
{
if (null !== $closure) {
$this->rules[] = $closure;
return $this;
}
return $this->rules[] = new ExprBuilder($this->node);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition\Builder;
use Symfony\Component\Config\Definition\VariableNode;
/**
* This class provides a fluent interface for defining a node.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class VariableNodeDefinition extends NodeDefinition
{
/**
* Instantiate a Node.
*
* @return VariableNode The node
*/
protected function instantiateNode()
{
return new VariableNode($this->name, $this->parent);
}
/**
* {@inheritdoc}
*/
protected function createNode()
{
$node = $this->instantiateNode();
if (null !== $this->normalization) {
$node->setNormalizationClosures($this->normalization->before);
}
if (null !== $this->merge) {
$node->setAllowOverwrite($this->merge->allowOverwrite);
}
if (true === $this->default) {
$node->setDefaultValue($this->defaultValue);
}
$node->setAllowEmptyValue($this->allowEmptyValue);
$node->addEquivalentValue(null, $this->nullEquivalent);
$node->addEquivalentValue(true, $this->trueEquivalent);
$node->addEquivalentValue(false, $this->falseEquivalent);
$node->setRequired($this->required);
if (null !== $this->validation) {
$node->setFinalValidationClosures($this->validation->rules);
}
return $node;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition;
/**
* Configuration interface.
*
* @author Victor Berchet <victor@suumit.com>
*/
interface ConfigurationInterface
{
/**
* Generates the configuration tree builder.
*
* @return \Symfony\Component\Config\Definition\Builder\TreeBuilder The tree builder
*/
public function getConfigTreeBuilder();
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition\Dumper;
use Symfony\Component\Config\Definition\ConfigurationInterface;
use Symfony\Component\Config\Definition\NodeInterface;
use Symfony\Component\Config\Definition\ArrayNode;
use Symfony\Component\Config\Definition\EnumNode;
use Symfony\Component\Config\Definition\PrototypedArrayNode;
/**
* Dumps a XML reference configuration for the given configuration/node instance.
*
* @author Wouter J <waldio.webdesign@gmail.com>
*/
class XmlReferenceDumper
{
private $reference;
public function dump(ConfigurationInterface $configuration, $namespace = null)
{
return $this->dumpNode($configuration->getConfigTreeBuilder()->buildTree(), $namespace);
}
public function dumpNode(NodeInterface $node, $namespace = null)
{
$this->reference = '';
$this->writeNode($node, 0, true, $namespace);
$ref = $this->reference;
$this->reference = null;
return $ref;
}
/**
* @param NodeInterface $node
* @param int $depth
* @param bool $root If the node is the root node
* @param string $namespace The namespace of the node
*/
private function writeNode(NodeInterface $node, $depth = 0, $root = false, $namespace = null)
{
$rootName = ($root ? 'config' : $node->getName());
$rootNamespace = ($namespace ?: ($root ? 'http://example.org/schema/dic/'.$node->getName() : null));
// xml remapping
if ($node->getParent()) {
$remapping = array_filter($node->getParent()->getXmlRemappings(), function ($mapping) use ($rootName) {
return $rootName === $mapping[1];
});
if (count($remapping)) {
list($singular) = current($remapping);
$rootName = $singular;
}
}
$rootName = str_replace('_', '-', $rootName);
$rootAttributes = array();
$rootAttributeComments = array();
$rootChildren = array();
$rootComments = array();
if ($node instanceof ArrayNode) {
$children = $node->getChildren();
// comments about the root node
if ($rootInfo = $node->getInfo()) {
$rootComments[] = $rootInfo;
}
if ($rootNamespace) {
$rootComments[] = 'Namespace: '.$rootNamespace;
}
// render prototyped nodes
if ($node instanceof PrototypedArrayNode) {
$prototype = $node->getPrototype();
$info = 'prototype';
if (null !== $prototype->getInfo()) {
$info .= ': '.$prototype->getInfo();
}
array_unshift($rootComments, $info);
if ($key = $node->getKeyAttribute()) {
$rootAttributes[$key] = str_replace('-', ' ', $rootName).' '.$key;
}
if ($prototype instanceof ArrayNode) {
$children = $prototype->getChildren();
} else {
if ($prototype->hasDefaultValue()) {
$prototypeValue = $prototype->getDefaultValue();
} else {
switch (get_class($prototype)) {
case 'Symfony\Component\Config\Definition\ScalarNode':
$prototypeValue = 'scalar value';
break;
case 'Symfony\Component\Config\Definition\FloatNode':
case 'Symfony\Component\Config\Definition\IntegerNode':
$prototypeValue = 'numeric value';
break;
case 'Symfony\Component\Config\Definition\BooleanNode':
$prototypeValue = 'true|false';
break;
case 'Symfony\Component\Config\Definition\EnumNode':
$prototypeValue = implode('|', array_map('json_encode', $prototype->getValues()));
break;
default:
$prototypeValue = 'value';
}
}
}
}
// get attributes and elements
foreach ($children as $child) {
if (!$child instanceof ArrayNode) {
// get attributes
// metadata
$name = str_replace('_', '-', $child->getName());
$value = '%%%%not_defined%%%%'; // use a string which isn't used in the normal world
// comments
$comments = array();
if ($info = $child->getInfo()) {
$comments[] = $info;
}
if ($example = $child->getExample()) {
$comments[] = 'Example: '.$example;
}
if ($child->isRequired()) {
$comments[] = 'Required';
}
if ($child instanceof EnumNode) {
$comments[] = 'One of '.implode('; ', array_map('json_encode', $child->getValues()));
}
if (count($comments)) {
$rootAttributeComments[$name] = implode(";\n", $comments);
}
// default values
if ($child->hasDefaultValue()) {
$value = $child->getDefaultValue();
}
// append attribute
$rootAttributes[$name] = $value;
} else {
// get elements
$rootChildren[] = $child;
}
}
}
// render comments
// root node comment
if (count($rootComments)) {
foreach ($rootComments as $comment) {
$this->writeLine('<!-- '.$comment.' -->', $depth);
}
}
// attribute comments
if (count($rootAttributeComments)) {
foreach ($rootAttributeComments as $attrName => $comment) {
$commentDepth = $depth + 4 + strlen($attrName) + 2;
$commentLines = explode("\n", $comment);
$multiline = (count($commentLines) > 1);
$comment = implode(PHP_EOL.str_repeat(' ', $commentDepth), $commentLines);
if ($multiline) {
$this->writeLine('<!--', $depth);
$this->writeLine($attrName.': '.$comment, $depth + 4);
$this->writeLine('-->', $depth);
} else {
$this->writeLine('<!-- '.$attrName.': '.$comment.' -->', $depth);
}
}
}
// render start tag + attributes
$rootIsVariablePrototype = isset($prototypeValue);
$rootIsEmptyTag = (0 === count($rootChildren) && !$rootIsVariablePrototype);
$rootOpenTag = '<'.$rootName;
if (1 >= ($attributesCount = count($rootAttributes))) {
if (1 === $attributesCount) {
$rootOpenTag .= sprintf(' %s="%s"', current(array_keys($rootAttributes)), $this->writeValue(current($rootAttributes)));
}
$rootOpenTag .= $rootIsEmptyTag ? ' />' : '>';
if ($rootIsVariablePrototype) {
$rootOpenTag .= $prototypeValue.'</'.$rootName.'>';
}
$this->writeLine($rootOpenTag, $depth);
} else {
$this->writeLine($rootOpenTag, $depth);
$i = 1;
foreach ($rootAttributes as $attrName => $attrValue) {
$attr = sprintf('%s="%s"', $attrName, $this->writeValue($attrValue));
$this->writeLine($attr, $depth + 4);
if ($attributesCount === $i++) {
$this->writeLine($rootIsEmptyTag ? '/>' : '>', $depth);
if ($rootIsVariablePrototype) {
$rootOpenTag .= $prototypeValue.'</'.$rootName.'>';
}
}
}
}
// render children tags
foreach ($rootChildren as $child) {
$this->writeLine('');
$this->writeNode($child, $depth + 4);
}
// render end tag
if (!$rootIsEmptyTag && !$rootIsVariablePrototype) {
$this->writeLine('');
$rootEndTag = '</'.$rootName.'>';
$this->writeLine($rootEndTag, $depth);
}
}
/**
* Outputs a single config reference line.
*
* @param string $text
* @param int $indent
*/
private function writeLine($text, $indent = 0)
{
$indent = strlen($text) + $indent;
$format = '%'.$indent.'s';
$this->reference .= sprintf($format, $text).PHP_EOL;
}
/**
* Renders the string conversion of the value.
*
* @param mixed $value
*
* @return string
*/
private function writeValue($value)
{
if ('%%%%not_defined%%%%' === $value) {
return '';
}
if (is_string($value) || is_numeric($value)) {
return $value;
}
if (false === $value) {
return 'false';
}
if (true === $value) {
return 'true';
}
if (null === $value) {
return 'null';
}
if (empty($value)) {
return '';
}
if (is_array($value)) {
return implode(',', $value);
}
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition\Dumper;
use Symfony\Component\Config\Definition\ConfigurationInterface;
use Symfony\Component\Config\Definition\NodeInterface;
use Symfony\Component\Config\Definition\ArrayNode;
use Symfony\Component\Config\Definition\EnumNode;
use Symfony\Component\Config\Definition\PrototypedArrayNode;
use Symfony\Component\Yaml\Inline;
/**
* Dumps a Yaml reference configuration for the given configuration/node instance.
*
* @author Kevin Bond <kevinbond@gmail.com>
*/
class YamlReferenceDumper
{
private $reference;
public function dump(ConfigurationInterface $configuration)
{
return $this->dumpNode($configuration->getConfigTreeBuilder()->buildTree());
}
public function dumpNode(NodeInterface $node)
{
$this->reference = '';
$this->writeNode($node);
$ref = $this->reference;
$this->reference = null;
return $ref;
}
/**
* @param NodeInterface $node
* @param int $depth
*/
private function writeNode(NodeInterface $node, $depth = 0)
{
$comments = array();
$default = '';
$defaultArray = null;
$children = null;
$example = $node->getExample();
// defaults
if ($node instanceof ArrayNode) {
$children = $node->getChildren();
if ($node instanceof PrototypedArrayNode) {
$prototype = $node->getPrototype();
if ($prototype instanceof ArrayNode) {
$children = $prototype->getChildren();
}
// check for attribute as key
if ($key = $node->getKeyAttribute()) {
$keyNodeClass = 'Symfony\Component\Config\Definition\\'.($prototype instanceof ArrayNode ? 'ArrayNode' : 'ScalarNode');
$keyNode = new $keyNodeClass($key, $node);
$info = 'Prototype';
if (null !== $prototype->getInfo()) {
$info .= ': '.$prototype->getInfo();
}
$keyNode->setInfo($info);
// add children
foreach ($children as $childNode) {
$keyNode->addChild($childNode);
}
$children = array($key => $keyNode);
}
}
if (!$children) {
if ($node->hasDefaultValue() && count($defaultArray = $node->getDefaultValue())) {
$default = '';
} elseif (!is_array($example)) {
$default = '[]';
}
}
} elseif ($node instanceof EnumNode) {
$comments[] = 'One of '.implode('; ', array_map('json_encode', $node->getValues()));
$default = $node->hasDefaultValue() ? Inline::dump($node->getDefaultValue()) : '~';
} else {
$default = '~';
if ($node->hasDefaultValue()) {
$default = $node->getDefaultValue();
if (is_array($default)) {
if (count($defaultArray = $node->getDefaultValue())) {
$default = '';
} elseif (!is_array($example)) {
$default = '[]';
}
} else {
$default = Inline::dump($default);
}
}
}
// required?
if ($node->isRequired()) {
$comments[] = 'Required';
}
// example
if ($example && !is_array($example)) {
$comments[] = 'Example: '.$example;
}
$default = (string) $default != '' ? ' '.$default : '';
$comments = count($comments) ? '# '.implode(', ', $comments) : '';
$text = rtrim(sprintf('%-20s %s %s', $node->getName().':', $default, $comments), ' ');
if ($info = $node->getInfo()) {
$this->writeLine('');
// indenting multi-line info
$info = str_replace("\n", sprintf("\n%".($depth * 4).'s# ', ' '), $info);
$this->writeLine('# '.$info, $depth * 4);
}
$this->writeLine($text, $depth * 4);
// output defaults
if ($defaultArray) {
$this->writeLine('');
$message = count($defaultArray) > 1 ? 'Defaults' : 'Default';
$this->writeLine('# '.$message.':', $depth * 4 + 4);
$this->writeArray($defaultArray, $depth + 1);
}
if (is_array($example)) {
$this->writeLine('');
$message = count($example) > 1 ? 'Examples' : 'Example';
$this->writeLine('# '.$message.':', $depth * 4 + 4);
$this->writeArray($example, $depth + 1);
}
if ($children) {
foreach ($children as $childNode) {
$this->writeNode($childNode, $depth + 1);
}
}
}
/**
* Outputs a single config reference line.
*
* @param string $text
* @param int $indent
*/
private function writeLine($text, $indent = 0)
{
$indent = strlen($text) + $indent;
$format = '%'.$indent.'s';
$this->reference .= sprintf($format, $text)."\n";
}
private function writeArray(array $array, $depth)
{
$isIndexed = array_values($array) === $array;
foreach ($array as $key => $value) {
if (is_array($value)) {
$val = '';
} else {
$val = $value;
}
if ($isIndexed) {
$this->writeLine('- '.$val, $depth * 4);
} else {
$this->writeLine(sprintf('%-20s %s', $key.':', $val), $depth * 4);
}
if (is_array($value)) {
$this->writeArray($value, $depth + 1);
}
}
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition;
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
/**
* Node which only allows a finite set of values.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class EnumNode extends ScalarNode
{
private $values;
public function __construct($name, NodeInterface $parent = null, array $values = array())
{
$values = array_unique($values);
if (empty($values)) {
throw new \InvalidArgumentException('$values must contain at least one element.');
}
parent::__construct($name, $parent);
$this->values = $values;
}
public function getValues()
{
return $this->values;
}
protected function finalizeValue($value)
{
$value = parent::finalizeValue($value);
if (!in_array($value, $this->values, true)) {
$ex = new InvalidConfigurationException(sprintf(
'The value %s is not allowed for path "%s". Permissible values: %s',
json_encode($value),
$this->getPath(),
implode(', ', array_map('json_encode', $this->values))));
$ex->setPath($this->getPath());
throw $ex;
}
return $value;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition\Exception;
/**
* This exception is thrown whenever the key of an array is not unique. This can
* only be the case if the configuration is coming from an XML file.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class DuplicateKeyException extends InvalidConfigurationException
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition\Exception;
/**
* Base exception for all configuration exceptions.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class Exception extends \RuntimeException
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition\Exception;
/**
* This exception is thrown when a configuration path is overwritten from a
* subsequent configuration file, but the entry node specifically forbids this.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class ForbiddenOverwriteException extends InvalidConfigurationException
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition\Exception;
/**
* A very general exception which can be thrown whenever non of the more specific
* exceptions is suitable.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class InvalidConfigurationException extends Exception
{
private $path;
private $containsHints = false;
public function setPath($path)
{
$this->path = $path;
}
public function getPath()
{
return $this->path;
}
/**
* Adds extra information that is suffixed to the original exception message.
*
* @param string $hint
*/
public function addHint($hint)
{
if (!$this->containsHints) {
$this->message .= "\nHint: ".$hint;
$this->containsHints = true;
} else {
$this->message .= ', '.$hint;
}
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition\Exception;
/**
* Thrown when an error is detected in a node Definition.
*
* @author Victor Berchet <victor.berchet@suumit.com>
*/
class InvalidDefinitionException extends Exception
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition\Exception;
/**
* This exception is thrown if an invalid type is encountered.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class InvalidTypeException extends InvalidConfigurationException
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition\Exception;
/**
* This exception is usually not encountered by the end-user, but only used
* internally to signal the parent scope to unset a key.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class UnsetKeyException extends Exception
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition;
use Symfony\Component\Config\Definition\Exception\InvalidTypeException;
/**
* This node represents a float value in the config tree.
*
* @author Jeanmonod David <david.jeanmonod@gmail.com>
*/
class FloatNode extends NumericNode
{
/**
* {@inheritdoc}
*/
protected function validateType($value)
{
// Integers are also accepted, we just cast them
if (is_int($value)) {
$value = (float) $value;
}
if (!is_float($value)) {
$ex = new InvalidTypeException(sprintf('Invalid type for path "%s". Expected float, but got %s.', $this->getPath(), gettype($value)));
if ($hint = $this->getInfo()) {
$ex->addHint($hint);
}
$ex->setPath($this->getPath());
throw $ex;
}
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition;
use Symfony\Component\Config\Definition\Exception\InvalidTypeException;
/**
* This node represents an integer value in the config tree.
*
* @author Jeanmonod David <david.jeanmonod@gmail.com>
*/
class IntegerNode extends NumericNode
{
/**
* {@inheritdoc}
*/
protected function validateType($value)
{
if (!is_int($value)) {
$ex = new InvalidTypeException(sprintf('Invalid type for path "%s". Expected int, but got %s.', $this->getPath(), gettype($value)));
if ($hint = $this->getInfo()) {
$ex->addHint($hint);
}
$ex->setPath($this->getPath());
throw $ex;
}
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition;
/**
* Common Interface among all nodes.
*
* In most cases, it is better to inherit from BaseNode instead of implementing
* this interface yourself.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
interface NodeInterface
{
/**
* Returns the name of the node.
*
* @return string The name of the node
*/
public function getName();
/**
* Returns the path of the node.
*
* @return string The node path
*/
public function getPath();
/**
* Returns true when the node is required.
*
* @return bool If the node is required
*/
public function isRequired();
/**
* Returns true when the node has a default value.
*
* @return bool If the node has a default value
*/
public function hasDefaultValue();
/**
* Returns the default value of the node.
*
* @return mixed The default value
*
* @throws \RuntimeException if the node has no default value
*/
public function getDefaultValue();
/**
* Normalizes the supplied value.
*
* @param mixed $value The value to normalize
*
* @return mixed The normalized value
*/
public function normalize($value);
/**
* Merges two values together.
*
* @param mixed $leftSide
* @param mixed $rightSide
*
* @return mixed The merged values
*/
public function merge($leftSide, $rightSide);
/**
* Finalizes a value.
*
* @param mixed $value The value to finalize
*
* @return mixed The finalized value
*/
public function finalize($value);
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition;
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
/**
* This node represents a numeric value in the config tree.
*
* @author David Jeanmonod <david.jeanmonod@gmail.com>
*/
class NumericNode extends ScalarNode
{
protected $min;
protected $max;
public function __construct($name, NodeInterface $parent = null, $min = null, $max = null)
{
parent::__construct($name, $parent);
$this->min = $min;
$this->max = $max;
}
/**
* {@inheritdoc}
*/
protected function finalizeValue($value)
{
$value = parent::finalizeValue($value);
$errorMsg = null;
if (isset($this->min) && $value < $this->min) {
$errorMsg = sprintf('The value %s is too small for path "%s". Should be greater than or equal to %s', $value, $this->getPath(), $this->min);
}
if (isset($this->max) && $value > $this->max) {
$errorMsg = sprintf('The value %s is too big for path "%s". Should be less than or equal to %s', $value, $this->getPath(), $this->max);
}
if (isset($errorMsg)) {
$ex = new InvalidConfigurationException($errorMsg);
$ex->setPath($this->getPath());
throw $ex;
}
return $value;
}
/**
* {@inheritdoc}
*/
protected function isValueEmpty($value)
{
// a numeric value cannot be empty
return false;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition;
/**
* This class is the entry point for config normalization/merging/finalization.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class Processor
{
/**
* Processes an array of configurations.
*
* @param NodeInterface $configTree The node tree describing the configuration
* @param array $configs An array of configuration items to process
*
* @return array The processed configuration
*/
public function process(NodeInterface $configTree, array $configs)
{
$currentConfig = array();
foreach ($configs as $config) {
$config = $configTree->normalize($config);
$currentConfig = $configTree->merge($currentConfig, $config);
}
return $configTree->finalize($currentConfig);
}
/**
* Processes an array of configurations.
*
* @param ConfigurationInterface $configuration The configuration class
* @param array $configs An array of configuration items to process
*
* @return array The processed configuration
*/
public function processConfiguration(ConfigurationInterface $configuration, array $configs)
{
return $this->process($configuration->getConfigTreeBuilder()->buildTree(), $configs);
}
/**
* Normalizes a configuration entry.
*
* This method returns a normalize configuration array for a given key
* to remove the differences due to the original format (YAML and XML mainly).
*
* Here is an example.
*
* The configuration in XML:
*
* <twig:extension>twig.extension.foo</twig:extension>
* <twig:extension>twig.extension.bar</twig:extension>
*
* And the same configuration in YAML:
*
* extensions: ['twig.extension.foo', 'twig.extension.bar']
*
* @param array $config A config array
* @param string $key The key to normalize
* @param string $plural The plural form of the key if it is irregular
*
* @return array
*/
public static function normalizeConfig($config, $key, $plural = null)
{
if (null === $plural) {
$plural = $key.'s';
}
if (isset($config[$plural])) {
return $config[$plural];
}
if (isset($config[$key])) {
if (is_string($config[$key]) || !is_int(key($config[$key]))) {
// only one
return array($config[$key]);
}
return $config[$key];
}
return array();
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition;
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
use Symfony\Component\Config\Definition\Exception\DuplicateKeyException;
use Symfony\Component\Config\Definition\Exception\UnsetKeyException;
use Symfony\Component\Config\Definition\Exception\Exception;
/**
* Represents a prototyped Array node in the config tree.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class PrototypedArrayNode extends ArrayNode
{
protected $prototype;
protected $keyAttribute;
protected $removeKeyAttribute = false;
protected $minNumberOfElements = 0;
protected $defaultValue = array();
protected $defaultChildren;
/**
* Sets the minimum number of elements that a prototype based node must
* contain. By default this is zero, meaning no elements.
*
* @param int $number
*/
public function setMinNumberOfElements($number)
{
$this->minNumberOfElements = $number;
}
/**
* Sets the attribute which value is to be used as key.
*
* This is useful when you have an indexed array that should be an
* associative array. You can select an item from within the array
* to be the key of the particular item. For example, if "id" is the
* "key", then:
*
* array(
* array('id' => 'my_name', 'foo' => 'bar'),
* );
*
* becomes
*
* array(
* 'my_name' => array('foo' => 'bar'),
* );
*
* If you'd like "'id' => 'my_name'" to still be present in the resulting
* array, then you can set the second argument of this method to false.
*
* @param string $attribute The name of the attribute which value is to be used as a key
* @param bool $remove Whether or not to remove the key
*/
public function setKeyAttribute($attribute, $remove = true)
{
$this->keyAttribute = $attribute;
$this->removeKeyAttribute = $remove;
}
/**
* Retrieves the name of the attribute which value should be used as key.
*
* @return string The name of the attribute
*/
public function getKeyAttribute()
{
return $this->keyAttribute;
}
/**
* Sets the default value of this node.
*
* @param string $value
*
* @throws \InvalidArgumentException if the default value is not an array
*/
public function setDefaultValue($value)
{
if (!is_array($value)) {
throw new \InvalidArgumentException($this->getPath().': the default value of an array node has to be an array.');
}
$this->defaultValue = $value;
}
/**
* Checks if the node has a default value.
*
* @return bool
*/
public function hasDefaultValue()
{
return true;
}
/**
* Adds default children when none are set.
*
* @param int|string|array|null $children The number of children|The child name|The children names to be added
*/
public function setAddChildrenIfNoneSet($children = array('defaults'))
{
if (null === $children) {
$this->defaultChildren = array('defaults');
} else {
$this->defaultChildren = is_int($children) && $children > 0 ? range(1, $children) : (array) $children;
}
}
/**
* Retrieves the default value.
*
* The default value could be either explicited or derived from the prototype
* default value.
*
* @return array The default value
*/
public function getDefaultValue()
{
if (null !== $this->defaultChildren) {
$default = $this->prototype->hasDefaultValue() ? $this->prototype->getDefaultValue() : array();
$defaults = array();
foreach (array_values($this->defaultChildren) as $i => $name) {
$defaults[null === $this->keyAttribute ? $i : $name] = $default;
}
return $defaults;
}
return $this->defaultValue;
}
/**
* Sets the node prototype.
*
* @param PrototypeNodeInterface $node
*/
public function setPrototype(PrototypeNodeInterface $node)
{
$this->prototype = $node;
}
/**
* Retrieves the prototype.
*
* @return PrototypeNodeInterface The prototype
*/
public function getPrototype()
{
return $this->prototype;
}
/**
* Disable adding concrete children for prototyped nodes.
*
* @param NodeInterface $node The child node to add
*
* @throws Exception
*/
public function addChild(NodeInterface $node)
{
throw new Exception('A prototyped array node can not have concrete children.');
}
/**
* Finalizes the value of this node.
*
* @param mixed $value
*
* @return mixed The finalized value
*
* @throws UnsetKeyException
* @throws InvalidConfigurationException if the node doesn't have enough children
*/
protected function finalizeValue($value)
{
if (false === $value) {
$msg = sprintf('Unsetting key for path "%s", value: %s', $this->getPath(), json_encode($value));
throw new UnsetKeyException($msg);
}
foreach ($value as $k => $v) {
$this->prototype->setName($k);
try {
$value[$k] = $this->prototype->finalize($v);
} catch (UnsetKeyException $e) {
unset($value[$k]);
}
}
if (count($value) < $this->minNumberOfElements) {
$msg = sprintf('The path "%s" should have at least %d element(s) defined.', $this->getPath(), $this->minNumberOfElements);
$ex = new InvalidConfigurationException($msg);
$ex->setPath($this->getPath());
throw $ex;
}
return $value;
}
/**
* Normalizes the value.
*
* @param mixed $value The value to normalize
*
* @return mixed The normalized value
*
* @throws InvalidConfigurationException
* @throws DuplicateKeyException
*/
protected function normalizeValue($value)
{
if (false === $value) {
return $value;
}
$value = $this->remapXml($value);
$isAssoc = array_keys($value) !== range(0, count($value) - 1);
$normalized = array();
foreach ($value as $k => $v) {
if (null !== $this->keyAttribute && is_array($v)) {
if (!isset($v[$this->keyAttribute]) && is_int($k) && !$isAssoc) {
$msg = sprintf('The attribute "%s" must be set for path "%s".', $this->keyAttribute, $this->getPath());
$ex = new InvalidConfigurationException($msg);
$ex->setPath($this->getPath());
throw $ex;
} elseif (isset($v[$this->keyAttribute])) {
$k = $v[$this->keyAttribute];
// remove the key attribute when required
if ($this->removeKeyAttribute) {
unset($v[$this->keyAttribute]);
}
// if only "value" is left
if (1 == count($v) && isset($v['value'])) {
$v = $v['value'];
}
}
if (array_key_exists($k, $normalized)) {
$msg = sprintf('Duplicate key "%s" for path "%s".', $k, $this->getPath());
$ex = new DuplicateKeyException($msg);
$ex->setPath($this->getPath());
throw $ex;
}
}
$this->prototype->setName($k);
if (null !== $this->keyAttribute || $isAssoc) {
$normalized[$k] = $this->prototype->normalize($v);
} else {
$normalized[] = $this->prototype->normalize($v);
}
}
return $normalized;
}
/**
* Merges values together.
*
* @param mixed $leftSide The left side to merge
* @param mixed $rightSide The right side to merge
*
* @return mixed The merged values
*
* @throws InvalidConfigurationException
* @throws \RuntimeException
*/
protected function mergeValues($leftSide, $rightSide)
{
if (false === $rightSide) {
// if this is still false after the last config has been merged the
// finalization pass will take care of removing this key entirely
return false;
}
if (false === $leftSide || !$this->performDeepMerging) {
return $rightSide;
}
foreach ($rightSide as $k => $v) {
// prototype, and key is irrelevant, so simply append the element
if (null === $this->keyAttribute) {
$leftSide[] = $v;
continue;
}
// no conflict
if (!array_key_exists($k, $leftSide)) {
if (!$this->allowNewKeys) {
$ex = new InvalidConfigurationException(sprintf(
'You are not allowed to define new elements for path "%s". '.
'Please define all elements for this path in one config file.',
$this->getPath()
));
$ex->setPath($this->getPath());
throw $ex;
}
$leftSide[$k] = $v;
continue;
}
$this->prototype->setName($k);
$leftSide[$k] = $this->prototype->merge($leftSide[$k], $v);
}
return $leftSide;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition;
/**
* This interface must be implemented by nodes which can be used as prototypes.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
interface PrototypeNodeInterface extends NodeInterface
{
/**
* Sets the name of the node.
*
* @param string $name The name of the node
*/
public function setName($name);
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition;
use Symfony\Component\Config\Definition\Exception\InvalidTypeException;
/**
* This node represents a scalar value in the config tree.
*
* The following values are considered scalars:
* * booleans
* * strings
* * null
* * integers
* * floats
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class ScalarNode extends VariableNode
{
/**
* {@inheritdoc}
*/
protected function validateType($value)
{
if (!is_scalar($value) && null !== $value) {
$ex = new InvalidTypeException(sprintf(
'Invalid type for path "%s". Expected scalar, but got %s.',
$this->getPath(),
gettype($value)
));
if ($hint = $this->getInfo()) {
$ex->addHint($hint);
}
$ex->setPath($this->getPath());
throw $ex;
}
}
/**
* {@inheritdoc}
*/
protected function isValueEmpty($value)
{
return null === $value || '' === $value;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Definition;
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
/**
* This node represents a value of variable type in the config tree.
*
* This node is intended for values of arbitrary type.
* Any PHP type is accepted as a value.
*
* @author Jeremy Mikola <jmikola@gmail.com>
*/
class VariableNode extends BaseNode implements PrototypeNodeInterface
{
protected $defaultValueSet = false;
protected $defaultValue;
protected $allowEmptyValue = true;
/**
* {@inheritdoc}
*/
public function setDefaultValue($value)
{
$this->defaultValueSet = true;
$this->defaultValue = $value;
}
/**
* {@inheritdoc}
*/
public function hasDefaultValue()
{
return $this->defaultValueSet;
}
/**
* {@inheritdoc}
*/
public function getDefaultValue()
{
$v = $this->defaultValue;
return $v instanceof \Closure ? $v() : $v;
}
/**
* Sets if this node is allowed to have an empty value.
*
* @param bool $boolean True if this entity will accept empty values
*/
public function setAllowEmptyValue($boolean)
{
$this->allowEmptyValue = (bool) $boolean;
}
/**
* {@inheritdoc}
*/
public function setName($name)
{
$this->name = $name;
}
/**
* {@inheritdoc}
*/
protected function validateType($value)
{
}
/**
* {@inheritdoc}
*/
protected function finalizeValue($value)
{
if (!$this->allowEmptyValue && $this->isValueEmpty($value)) {
$ex = new InvalidConfigurationException(sprintf(
'The path "%s" cannot contain an empty value, but got %s.',
$this->getPath(),
json_encode($value)
));
if ($hint = $this->getInfo()) {
$ex->addHint($hint);
}
$ex->setPath($this->getPath());
throw $ex;
}
return $value;
}
/**
* {@inheritdoc}
*/
protected function normalizeValue($value)
{
return $value;
}
/**
* {@inheritdoc}
*/
protected function mergeValues($leftSide, $rightSide)
{
return $rightSide;
}
/**
* Evaluates if the given value is to be treated as empty.
*
* By default, PHP's empty() function is used to test for emptiness. This
* method may be overridden by subtypes to better match their understanding
* of empty data.
*
* @param mixed $value
*
* @return bool
*/
protected function isValueEmpty($value)
{
return empty($value);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Exception;
/**
* Exception class for when a circular reference is detected when importing resources.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class FileLoaderImportCircularReferenceException extends FileLoaderLoadException
{
public function __construct(array $resources, $code = null, $previous = null)
{
$message = sprintf('Circular reference detected in "%s" ("%s" > "%s").', $this->varToString($resources[0]), implode('" > "', $resources), $resources[0]);
\Exception::__construct($message, $code, $previous);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Exception;
/**
* Exception class for when a resource cannot be loaded or imported.
*
* @author Ryan Weaver <ryan@thatsquality.com>
*/
class FileLoaderLoadException extends \Exception
{
/**
* @param string $resource The resource that could not be imported
* @param string $sourceResource The original resource importing the new resource
* @param int $code The error code
* @param \Exception $previous A previous exception
*/
public function __construct($resource, $sourceResource = null, $code = null, $previous = null)
{
$message = '';
if ($previous) {
// Include the previous exception, to help the user see what might be the underlying cause
// Trim the trailing period of the previous message. We only want 1 period remove so no rtrim...
if ('.' === substr($previous->getMessage(), -1)) {
$trimmedMessage = substr($previous->getMessage(), 0, -1);
$message .= sprintf('%s', $trimmedMessage).' in ';
} else {
$message .= sprintf('%s', $previous->getMessage()).' in ';
}
$message .= $resource.' ';
// show tweaked trace to complete the human readable sentence
if (null === $sourceResource) {
$message .= sprintf('(which is loaded in resource "%s")', $this->varToString($resource));
} else {
$message .= sprintf('(which is being imported from "%s")', $this->varToString($sourceResource));
}
$message .= '.';
// if there's no previous message, present it the default way
} elseif (null === $sourceResource) {
$message .= sprintf('Cannot load resource "%s".', $this->varToString($resource));
} else {
$message .= sprintf('Cannot import resource "%s" from "%s".', $this->varToString($resource), $this->varToString($sourceResource));
}
// Is the resource located inside a bundle?
if ('@' === $resource[0]) {
$parts = explode(DIRECTORY_SEPARATOR, $resource);
$bundle = substr($parts[0], 1);
$message .= sprintf(' Make sure the "%s" bundle is correctly registered and loaded in the application kernel class.', $bundle);
$message .= sprintf(' If the bundle is registered, make sure the bundle path "%s" is not empty.', $resource);
}
parent::__construct($message, $code, $previous);
}
protected function varToString($var)
{
if (is_object($var)) {
return sprintf('Object(%s)', get_class($var));
}
if (is_array($var)) {
$a = array();
foreach ($var as $k => $v) {
$a[] = sprintf('%s => %s', $k, $this->varToString($v));
}
return sprintf('Array(%s)', implode(', ', $a));
}
if (is_resource($var)) {
return sprintf('Resource(%s)', get_resource_type($var));
}
if (null === $var) {
return 'null';
}
if (false === $var) {
return 'false';
}
if (true === $var) {
return 'true';
}
return (string) $var;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config;
/**
* FileLocator uses an array of pre-defined paths to find files.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class FileLocator implements FileLocatorInterface
{
protected $paths;
/**
* Constructor.
*
* @param string|array $paths A path or an array of paths where to look for resources
*/
public function __construct($paths = array())
{
$this->paths = (array) $paths;
}
/**
* {@inheritdoc}
*/
public function locate($name, $currentPath = null, $first = true)
{
if ('' == $name) {
throw new \InvalidArgumentException('An empty file name is not valid to be located.');
}
if ($this->isAbsolutePath($name)) {
if (!file_exists($name)) {
throw new \InvalidArgumentException(sprintf('The file "%s" does not exist.', $name));
}
return $name;
}
$paths = $this->paths;
if (null !== $currentPath) {
array_unshift($paths, $currentPath);
}
$paths = array_unique($paths);
$filepaths = array();
foreach ($paths as $path) {
if (@file_exists($file = $path.DIRECTORY_SEPARATOR.$name)) {
if (true === $first) {
return $file;
}
$filepaths[] = $file;
}
}
if (!$filepaths) {
throw new \InvalidArgumentException(sprintf('The file "%s" does not exist (in: %s).', $name, implode(', ', $paths)));
}
return $filepaths;
}
/**
* Returns whether the file path is an absolute path.
*
* @param string $file A file path
*
* @return bool
*/
private function isAbsolutePath($file)
{
if ($file[0] === '/' || $file[0] === '\\'
|| (strlen($file) > 3 && ctype_alpha($file[0])
&& $file[1] === ':'
&& ($file[2] === '\\' || $file[2] === '/')
)
|| null !== parse_url($file, PHP_URL_SCHEME)
) {
return true;
}
return false;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config;
/**
* @author Fabien Potencier <fabien@symfony.com>
*/
interface FileLocatorInterface
{
/**
* Returns a full path for a given file name.
*
* @param string $name The file name to locate
* @param string|null $currentPath The current path
* @param bool $first Whether to return the first occurrence or an array of filenames
*
* @return string|array The full path to the file or an array of file paths
*
* @throws \InvalidArgumentException When file is not found
*/
public function locate($name, $currentPath = null, $first = true);
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Loader;
use Symfony\Component\Config\Exception\FileLoaderLoadException;
/**
* DelegatingLoader delegates loading to other loaders using a loader resolver.
*
* This loader acts as an array of LoaderInterface objects - each having
* a chance to load a given resource (handled by the resolver)
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class DelegatingLoader extends Loader
{
/**
* Constructor.
*
* @param LoaderResolverInterface $resolver A LoaderResolverInterface instance
*/
public function __construct(LoaderResolverInterface $resolver)
{
$this->resolver = $resolver;
}
/**
* {@inheritdoc}
*/
public function load($resource, $type = null)
{
if (false === $loader = $this->resolver->resolve($resource, $type)) {
throw new FileLoaderLoadException($resource);
}
return $loader->load($resource, $type);
}
/**
* {@inheritdoc}
*/
public function supports($resource, $type = null)
{
return false !== $this->resolver->resolve($resource, $type);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Loader;
use Symfony\Component\Config\FileLocatorInterface;
use Symfony\Component\Config\Exception\FileLoaderLoadException;
use Symfony\Component\Config\Exception\FileLoaderImportCircularReferenceException;
/**
* FileLoader is the abstract class used by all built-in loaders that are file based.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
abstract class FileLoader extends Loader
{
/**
* @var array
*/
protected static $loading = array();
/**
* @var FileLocatorInterface
*/
protected $locator;
private $currentDir;
/**
* Constructor.
*
* @param FileLocatorInterface $locator A FileLocatorInterface instance
*/
public function __construct(FileLocatorInterface $locator)
{
$this->locator = $locator;
}
/**
* Sets the current directory.
*
* @param string $dir
*/
public function setCurrentDir($dir)
{
$this->currentDir = $dir;
}
/**
* Returns the file locator used by this loader.
*
* @return FileLocatorInterface
*/
public function getLocator()
{
return $this->locator;
}
/**
* Imports a resource.
*
* @param mixed $resource A Resource
* @param string|null $type The resource type or null if unknown
* @param bool $ignoreErrors Whether to ignore import errors or not
* @param string|null $sourceResource The original resource importing the new resource
*
* @return mixed
*
* @throws FileLoaderLoadException
* @throws FileLoaderImportCircularReferenceException
*/
public function import($resource, $type = null, $ignoreErrors = false, $sourceResource = null)
{
try {
$loader = $this->resolve($resource, $type);
if ($loader instanceof self && null !== $this->currentDir) {
$resource = $loader->getLocator()->locate($resource, $this->currentDir, false);
}
$resources = is_array($resource) ? $resource : array($resource);
for ($i = 0; $i < $resourcesCount = count($resources); ++$i) {
if (isset(self::$loading[$resources[$i]])) {
if ($i == $resourcesCount - 1) {
throw new FileLoaderImportCircularReferenceException(array_keys(self::$loading));
}
} else {
$resource = $resources[$i];
break;
}
}
self::$loading[$resource] = true;
try {
$ret = $loader->load($resource, $type);
} finally {
unset(self::$loading[$resource]);
}
return $ret;
} catch (FileLoaderImportCircularReferenceException $e) {
throw $e;
} catch (\Exception $e) {
if (!$ignoreErrors) {
// prevent embedded imports from nesting multiple exceptions
if ($e instanceof FileLoaderLoadException) {
throw $e;
}
throw new FileLoaderLoadException($resource, $sourceResource, null, $e);
}
}
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Loader;
use Symfony\Component\Config\Exception\FileLoaderLoadException;
/**
* Loader is the abstract class used by all built-in loaders.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
abstract class Loader implements LoaderInterface
{
protected $resolver;
/**
* {@inheritdoc}
*/
public function getResolver()
{
return $this->resolver;
}
/**
* {@inheritdoc}
*/
public function setResolver(LoaderResolverInterface $resolver)
{
$this->resolver = $resolver;
}
/**
* Imports a resource.
*
* @param mixed $resource A resource
* @param string|null $type The resource type or null if unknown
*
* @return mixed
*/
public function import($resource, $type = null)
{
return $this->resolve($resource, $type)->load($resource, $type);
}
/**
* Finds a loader able to load an imported resource.
*
* @param mixed $resource A resource
* @param string|null $type The resource type or null if unknown
*
* @return LoaderInterface A LoaderInterface instance
*
* @throws FileLoaderLoadException If no loader is found
*/
public function resolve($resource, $type = null)
{
if ($this->supports($resource, $type)) {
return $this;
}
$loader = null === $this->resolver ? false : $this->resolver->resolve($resource, $type);
if (false === $loader) {
throw new FileLoaderLoadException($resource);
}
return $loader;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Loader;
/**
* LoaderInterface is the interface implemented by all loader classes.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
interface LoaderInterface
{
/**
* Loads a resource.
*
* @param mixed $resource The resource
* @param string|null $type The resource type or null if unknown
*
* @throws \Exception If something went wrong
*/
public function load($resource, $type = null);
/**
* Returns whether this class supports the given resource.
*
* @param mixed $resource A resource
* @param string|null $type The resource type or null if unknown
*
* @return bool True if this class supports the given resource, false otherwise
*/
public function supports($resource, $type = null);
/**
* Gets the loader resolver.
*
* @return LoaderResolverInterface A LoaderResolverInterface instance
*/
public function getResolver();
/**
* Sets the loader resolver.
*
* @param LoaderResolverInterface $resolver A LoaderResolverInterface instance
*/
public function setResolver(LoaderResolverInterface $resolver);
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Loader;
/**
* LoaderResolver selects a loader for a given resource.
*
* A resource can be anything (e.g. a full path to a config file or a Closure).
* Each loader determines whether it can load a resource and how.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class LoaderResolver implements LoaderResolverInterface
{
/**
* @var LoaderInterface[] An array of LoaderInterface objects
*/
private $loaders = array();
/**
* Constructor.
*
* @param LoaderInterface[] $loaders An array of loaders
*/
public function __construct(array $loaders = array())
{
foreach ($loaders as $loader) {
$this->addLoader($loader);
}
}
/**
* {@inheritdoc}
*/
public function resolve($resource, $type = null)
{
foreach ($this->loaders as $loader) {
if ($loader->supports($resource, $type)) {
return $loader;
}
}
return false;
}
/**
* Adds a loader.
*
* @param LoaderInterface $loader A LoaderInterface instance
*/
public function addLoader(LoaderInterface $loader)
{
$this->loaders[] = $loader;
$loader->setResolver($this);
}
/**
* Returns the registered loaders.
*
* @return LoaderInterface[] An array of LoaderInterface instances
*/
public function getLoaders()
{
return $this->loaders;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Loader;
/**
* LoaderResolverInterface selects a loader for a given resource.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
interface LoaderResolverInterface
{
/**
* Returns a loader able to load the resource.
*
* @param mixed $resource A resource
* @param string|null $type The resource type or null if unknown
*
* @return LoaderInterface|false The loader or false if none is able to load the resource
*/
public function resolve($resource, $type = null);
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Resource;
/**
* DirectoryResource represents a resources stored in a subdirectory tree.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class DirectoryResource implements SelfCheckingResourceInterface, \Serializable
{
private $resource;
private $pattern;
/**
* Constructor.
*
* @param string $resource The file path to the resource
* @param string|null $pattern A pattern to restrict monitored files
*
* @throws \InvalidArgumentException
*/
public function __construct($resource, $pattern = null)
{
$this->resource = realpath($resource);
$this->pattern = $pattern;
if (false === $this->resource || !is_dir($this->resource)) {
throw new \InvalidArgumentException(sprintf('The directory "%s" does not exist.', $resource));
}
}
/**
* {@inheritdoc}
*/
public function __toString()
{
return md5(serialize(array($this->resource, $this->pattern)));
}
/**
* @return string The file path to the resource
*/
public function getResource()
{
return $this->resource;
}
/**
* Returns the pattern to restrict monitored files.
*
* @return string|null
*/
public function getPattern()
{
return $this->pattern;
}
/**
* {@inheritdoc}
*/
public function isFresh($timestamp)
{
if (!is_dir($this->resource)) {
return false;
}
$newestMTime = filemtime($this->resource);
foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->resource), \RecursiveIteratorIterator::SELF_FIRST) as $file) {
// if regex filtering is enabled only check matching files
if ($this->pattern && $file->isFile() && !preg_match($this->pattern, $file->getBasename())) {
continue;
}
// always monitor directories for changes, except the .. entries
// (otherwise deleted files wouldn't get detected)
if ($file->isDir() && '/..' === substr($file, -3)) {
continue;
}
$newestMTime = max($file->getMTime(), $newestMTime);
}
return $newestMTime < $timestamp;
}
public function serialize()
{
return serialize(array($this->resource, $this->pattern));
}
public function unserialize($serialized)
{
list($this->resource, $this->pattern) = unserialize($serialized);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Resource;
/**
* FileExistenceResource represents a resource stored on the filesystem.
* Freshness is only evaluated against resource creation or deletion.
*
* The resource can be a file or a directory.
*
* @author Charles-Henri Bruyand <charleshenri.bruyand@gmail.com>
*/
class FileExistenceResource implements SelfCheckingResourceInterface, \Serializable
{
private $resource;
private $exists;
/**
* Constructor.
*
* @param string $resource The file path to the resource
*/
public function __construct($resource)
{
$this->resource = (string) $resource;
$this->exists = file_exists($resource);
}
/**
* {@inheritdoc}
*/
public function __toString()
{
return $this->resource;
}
/**
* @return string The file path to the resource
*/
public function getResource()
{
return $this->resource;
}
/**
* {@inheritdoc}
*/
public function isFresh($timestamp)
{
return file_exists($this->resource) === $this->exists;
}
/**
* {@inheritdoc}
*/
public function serialize()
{
return serialize(array($this->resource, $this->exists));
}
/**
* {@inheritdoc}
*/
public function unserialize($serialized)
{
list($this->resource, $this->exists) = unserialize($serialized);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Resource;
/**
* FileResource represents a resource stored on the filesystem.
*
* The resource can be a file or a directory.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class FileResource implements SelfCheckingResourceInterface, \Serializable
{
/**
* @var string|false
*/
private $resource;
/**
* Constructor.
*
* @param string $resource The file path to the resource
*
* @throws \InvalidArgumentException
*/
public function __construct($resource)
{
$this->resource = realpath($resource);
if (false === $this->resource && file_exists($resource)) {
$this->resource = $resource;
}
if (false === $this->resource) {
throw new \InvalidArgumentException(sprintf('The file "%s" does not exist.', $resource));
}
}
/**
* {@inheritdoc}
*/
public function __toString()
{
return $this->resource;
}
/**
* @return string The canonicalized, absolute path to the resource
*/
public function getResource()
{
return $this->resource;
}
/**
* {@inheritdoc}
*/
public function isFresh($timestamp)
{
return file_exists($this->resource) && @filemtime($this->resource) <= $timestamp;
}
public function serialize()
{
return serialize($this->resource);
}
public function unserialize($serialized)
{
$this->resource = unserialize($serialized);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Resource;
/**
* ResourceInterface is the interface that must be implemented by all Resource classes.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
interface ResourceInterface
{
/**
* Returns a string representation of the Resource.
*
* This method is necessary to allow for resource de-duplication, for example by means
* of array_unique(). The string returned need not have a particular meaning, but has
* to be identical for different ResourceInterface instances referring to the same
* resource; and it should be unlikely to collide with that of other, unrelated
* resource instances.
*
* @return string A string representation unique to the underlying Resource
*/
public function __toString();
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Resource;
use Symfony\Component\Config\ResourceCheckerInterface;
/**
* Resource checker for instances of SelfCheckingResourceInterface.
*
* As these resources perform the actual check themselves, we can provide
* this class as a standard way of validating them.
*
* @author Matthias Pigulla <mp@webfactory.de>
*/
class SelfCheckingResourceChecker implements ResourceCheckerInterface
{
public function supports(ResourceInterface $metadata)
{
return $metadata instanceof SelfCheckingResourceInterface;
}
public function isFresh(ResourceInterface $resource, $timestamp)
{
/* @var SelfCheckingResourceInterface $resource */
return $resource->isFresh($timestamp);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Resource;
/**
* Interface for Resources that can check for freshness autonomously,
* without special support from external services.
*
* @author Matthias Pigulla <mp@webfactory.de>
*/
interface SelfCheckingResourceInterface extends ResourceInterface
{
/**
* Returns true if the resource has not been updated since the given timestamp.
*
* @param int $timestamp The last time the resource was loaded
*
* @return bool True if the resource has not been updated, false otherwise
*/
public function isFresh($timestamp);
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config;
use Symfony\Component\Config\Resource\ResourceInterface;
use Symfony\Component\Filesystem\Exception\IOException;
use Symfony\Component\Filesystem\Filesystem;
/**
* ResourceCheckerConfigCache uses instances of ResourceCheckerInterface
* to check whether cached data is still fresh.
*
* @author Matthias Pigulla <mp@webfactory.de>
*/
class ResourceCheckerConfigCache implements ConfigCacheInterface
{
/**
* @var string
*/
private $file;
/**
* @var ResourceCheckerInterface[]
*/
private $resourceCheckers;
/**
* @param string $file The absolute cache path
* @param ResourceCheckerInterface[] $resourceCheckers The ResourceCheckers to use for the freshness check
*/
public function __construct($file, array $resourceCheckers = array())
{
$this->file = $file;
$this->resourceCheckers = $resourceCheckers;
}
/**
* {@inheritdoc}
*/
public function getPath()
{
return $this->file;
}
/**
* Checks if the cache is still fresh.
*
* This implementation will make a decision solely based on the ResourceCheckers
* passed in the constructor.
*
* The first ResourceChecker that supports a given resource is considered authoritative.
* Resources with no matching ResourceChecker will silently be ignored and considered fresh.
*
* @return bool true if the cache is fresh, false otherwise
*/
public function isFresh()
{
if (!is_file($this->file)) {
return false;
}
if (!$this->resourceCheckers) {
return true; // shortcut - if we don't have any checkers we don't need to bother with the meta file at all
}
$metadata = $this->getMetaFile();
if (!is_file($metadata)) {
return true;
}
$time = filemtime($this->file);
$meta = unserialize(file_get_contents($metadata));
foreach ($meta as $resource) {
/* @var ResourceInterface $resource */
foreach ($this->resourceCheckers as $checker) {
if (!$checker->supports($resource)) {
continue; // next checker
}
if ($checker->isFresh($resource, $time)) {
break; // no need to further check this resource
}
return false; // cache is stale
}
// no suitable checker found, ignore this resource
}
return true;
}
/**
* Writes cache.
*
* @param string $content The content to write in the cache
* @param ResourceInterface[] $metadata An array of metadata
*
* @throws \RuntimeException When cache file can't be written
*/
public function write($content, array $metadata = null)
{
$mode = 0666;
$umask = umask();
$filesystem = new Filesystem();
$filesystem->dumpFile($this->file, $content, null);
try {
$filesystem->chmod($this->file, $mode, $umask);
} catch (IOException $e) {
// discard chmod failure (some filesystem may not support it)
}
if (null !== $metadata) {
$filesystem->dumpFile($this->getMetaFile(), serialize($metadata), null);
try {
$filesystem->chmod($this->getMetaFile(), $mode, $umask);
} catch (IOException $e) {
// discard chmod failure (some filesystem may not support it)
}
}
}
/**
* Gets the meta file path.
*
* @return string The meta file path
*/
private function getMetaFile()
{
return $this->file.'.meta';
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config;
/**
* A ConfigCacheFactory implementation that validates the
* cache with an arbitrary set of ResourceCheckers.
*
* @author Matthias Pigulla <mp@webfactory.de>
*/
class ResourceCheckerConfigCacheFactory implements ConfigCacheFactoryInterface
{
/**
* @var ResourceCheckerInterface[]
*/
private $resourceCheckers = array();
/**
* @param ResourceCheckerInterface[] $resourceCheckers
*/
public function __construct(array $resourceCheckers = array())
{
$this->resourceCheckers = $resourceCheckers;
}
/**
* {@inheritdoc}
*/
public function cache($file, $callback)
{
if (!is_callable($callback)) {
throw new \InvalidArgumentException(sprintf('Invalid type for callback argument. Expected callable, but got "%s".', gettype($callback)));
}
$cache = new ResourceCheckerConfigCache($file, $this->resourceCheckers);
if (!$cache->isFresh()) {
call_user_func($callback, $cache);
}
return $cache;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config;
use Symfony\Component\Config\Resource\ResourceInterface;
/**
* Interface for ResourceCheckers.
*
* When a ResourceCheckerConfigCache instance is checked for freshness, all its associated
* metadata resources are passed to ResourceCheckers. The ResourceCheckers
* can then inspect the resources and decide whether the cache can be considered
* fresh or not.
*
* @author Matthias Pigulla <mp@webfactory.de>
* @author Benjamin Klotz <bk@webfactory.de>
*/
interface ResourceCheckerInterface
{
/**
* Queries the ResourceChecker whether it can validate a given
* resource or not.
*
* @param ResourceInterface $metadata The resource to be checked for freshness
*
* @return bool True if the ResourceChecker can handle this resource type, false if not
*/
public function supports(ResourceInterface $metadata);
/**
* Validates the resource.
*
* @param ResourceInterface $resource The resource to be validated
* @param int $timestamp The timestamp at which the cache associated with this resource was created
*
* @return bool True if the resource has not changed since the given timestamp, false otherwise
*/
public function isFresh(ResourceInterface $resource, $timestamp);
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config\Util;
/**
* XMLUtils is a bunch of utility methods to XML operations.
*
* This class contains static methods only and is not meant to be instantiated.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Martin Hasoň <martin.hason@gmail.com>
*/
class XmlUtils
{
/**
* This class should not be instantiated.
*/
private function __construct()
{
}
/**
* Loads an XML file.
*
* @param string $file An XML file path
* @param string|callable|null $schemaOrCallable An XSD schema file path, a callable, or null to disable validation
*
* @return \DOMDocument
*
* @throws \InvalidArgumentException When loading of XML file returns error
*/
public static function loadFile($file, $schemaOrCallable = null)
{
$content = @file_get_contents($file);
if ('' === trim($content)) {
throw new \InvalidArgumentException(sprintf('File %s does not contain valid XML, it is empty.', $file));
}
$internalErrors = libxml_use_internal_errors(true);
$disableEntities = libxml_disable_entity_loader(true);
libxml_clear_errors();
$dom = new \DOMDocument();
$dom->validateOnParse = true;
if (!$dom->loadXML($content, LIBXML_NONET | (defined('LIBXML_COMPACT') ? LIBXML_COMPACT : 0))) {
libxml_disable_entity_loader($disableEntities);
throw new \InvalidArgumentException(implode("\n", static::getXmlErrors($internalErrors)));
}
$dom->normalizeDocument();
libxml_use_internal_errors($internalErrors);
libxml_disable_entity_loader($disableEntities);
foreach ($dom->childNodes as $child) {
if ($child->nodeType === XML_DOCUMENT_TYPE_NODE) {
throw new \InvalidArgumentException('Document types are not allowed.');
}
}
if (null !== $schemaOrCallable) {
$internalErrors = libxml_use_internal_errors(true);
libxml_clear_errors();
$e = null;
if (is_callable($schemaOrCallable)) {
try {
$valid = call_user_func($schemaOrCallable, $dom, $internalErrors);
} catch (\Exception $e) {
$valid = false;
}
} elseif (!is_array($schemaOrCallable) && is_file((string) $schemaOrCallable)) {
$schemaSource = file_get_contents((string) $schemaOrCallable);
$valid = @$dom->schemaValidateSource($schemaSource);
} else {
libxml_use_internal_errors($internalErrors);
throw new \InvalidArgumentException('The schemaOrCallable argument has to be a valid path to XSD file or callable.');
}
if (!$valid) {
$messages = static::getXmlErrors($internalErrors);
if (empty($messages)) {
$messages = array(sprintf('The XML file "%s" is not valid.', $file));
}
throw new \InvalidArgumentException(implode("\n", $messages), 0, $e);
}
}
libxml_clear_errors();
libxml_use_internal_errors($internalErrors);
return $dom;
}
/**
* Converts a \DomElement object to a PHP array.
*
* The following rules applies during the conversion:
*
* * Each tag is converted to a key value or an array
* if there is more than one "value"
*
* * The content of a tag is set under a "value" key (<foo>bar</foo>)
* if the tag also has some nested tags
*
* * The attributes are converted to keys (<foo foo="bar"/>)
*
* * The nested-tags are converted to keys (<foo><foo>bar</foo></foo>)
*
* @param \DomElement $element A \DomElement instance
* @param bool $checkPrefix Check prefix in an element or an attribute name
*
* @return array A PHP array
*/
public static function convertDomElementToArray(\DOMElement $element, $checkPrefix = true)
{
$prefix = (string) $element->prefix;
$empty = true;
$config = array();
foreach ($element->attributes as $name => $node) {
if ($checkPrefix && !in_array((string) $node->prefix, array('', $prefix), true)) {
continue;
}
$config[$name] = static::phpize($node->value);
$empty = false;
}
$nodeValue = false;
foreach ($element->childNodes as $node) {
if ($node instanceof \DOMText) {
if ('' !== trim($node->nodeValue)) {
$nodeValue = trim($node->nodeValue);
$empty = false;
}
} elseif ($checkPrefix && $prefix != (string) $node->prefix) {
continue;
} elseif (!$node instanceof \DOMComment) {
$value = static::convertDomElementToArray($node, $checkPrefix);
$key = $node->localName;
if (isset($config[$key])) {
if (!is_array($config[$key]) || !is_int(key($config[$key]))) {
$config[$key] = array($config[$key]);
}
$config[$key][] = $value;
} else {
$config[$key] = $value;
}
$empty = false;
}
}
if (false !== $nodeValue) {
$value = static::phpize($nodeValue);
if (count($config)) {
$config['value'] = $value;
} else {
$config = $value;
}
}
return !$empty ? $config : null;
}
/**
* Converts an xml value to a PHP type.
*
* @param mixed $value
*
* @return mixed
*/
public static function phpize($value)
{
$value = (string) $value;
$lowercaseValue = strtolower($value);
switch (true) {
case 'null' === $lowercaseValue:
return;
case ctype_digit($value):
$raw = $value;
$cast = (int) $value;
return '0' == $value[0] ? octdec($value) : (((string) $raw === (string) $cast) ? $cast : $raw);
case isset($value[1]) && '-' === $value[0] && ctype_digit(substr($value, 1)):
$raw = $value;
$cast = (int) $value;
return '0' == $value[1] ? octdec($value) : (((string) $raw === (string) $cast) ? $cast : $raw);
case 'true' === $lowercaseValue:
return true;
case 'false' === $lowercaseValue:
return false;
case isset($value[1]) && '0b' == $value[0].$value[1]:
return bindec($value);
case is_numeric($value):
return '0x' === $value[0].$value[1] ? hexdec($value) : (float) $value;
case preg_match('/^0x[0-9a-f]++$/i', $value):
return hexdec($value);
case preg_match('/^(-|\+)?[0-9]+(\.[0-9]+)?$/', $value):
return (float) $value;
default:
return $value;
}
}
protected static function getXmlErrors($internalErrors)
{
$errors = array();
foreach (libxml_get_errors() as $error) {
$errors[] = sprintf('[%s %s] %s (in %s - line %d, column %d)',
LIBXML_ERR_WARNING == $error->level ? 'WARNING' : 'ERROR',
$error->code,
trim($error->message),
$error->file ?: 'n/a',
$error->line,
$error->column
);
}
libxml_clear_errors();
libxml_use_internal_errors($internalErrors);
return $errors;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console;
use Symfony\Component\Console\Exception\ExceptionInterface;
use Symfony\Component\Console\Helper\DebugFormatterHelper;
use Symfony\Component\Console\Helper\ProcessHelper;
use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputAwareInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Command\HelpCommand;
use Symfony\Component\Console\Command\ListCommand;
use Symfony\Component\Console\Helper\HelperSet;
use Symfony\Component\Console\Helper\FormatterHelper;
use Symfony\Component\Console\Event\ConsoleCommandEvent;
use Symfony\Component\Console\Event\ConsoleExceptionEvent;
use Symfony\Component\Console\Event\ConsoleTerminateEvent;
use Symfony\Component\Console\Exception\CommandNotFoundException;
use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\Debug\Exception\FatalThrowableError;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
/**
* An Application is the container for a collection of commands.
*
* It is the main entry point of a Console application.
*
* This class is optimized for a standard CLI environment.
*
* Usage:
*
* $app = new Application('myapp', '1.0 (stable)');
* $app->add(new SimpleCommand());
* $app->run();
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class Application
{
private $commands = array();
private $wantHelps = false;
private $runningCommand;
private $name;
private $version;
private $catchExceptions = true;
private $autoExit = true;
private $definition;
private $helperSet;
private $dispatcher;
private $terminalDimensions;
private $defaultCommand;
/**
* Constructor.
*
* @param string $name The name of the application
* @param string $version The version of the application
*/
public function __construct($name = 'UNKNOWN', $version = 'UNKNOWN')
{
$this->name = $name;
$this->version = $version;
$this->defaultCommand = 'list';
$this->helperSet = $this->getDefaultHelperSet();
$this->definition = $this->getDefaultInputDefinition();
foreach ($this->getDefaultCommands() as $command) {
$this->add($command);
}
}
public function setDispatcher(EventDispatcherInterface $dispatcher)
{
$this->dispatcher = $dispatcher;
}
/**
* Runs the current application.
*
* @param InputInterface $input An Input instance
* @param OutputInterface $output An Output instance
*
* @return int 0 if everything went fine, or an error code
*
* @throws \Exception When doRun returns Exception
*/
public function run(InputInterface $input = null, OutputInterface $output = null)
{
if (null === $input) {
$input = new ArgvInput();
}
if (null === $output) {
$output = new ConsoleOutput();
}
$this->configureIO($input, $output);
try {
$exitCode = $this->doRun($input, $output);
} catch (\Exception $e) {
if (!$this->catchExceptions) {
throw $e;
}
if ($output instanceof ConsoleOutputInterface) {
$this->renderException($e, $output->getErrorOutput());
} else {
$this->renderException($e, $output);
}
$exitCode = $e->getCode();
if (is_numeric($exitCode)) {
$exitCode = (int) $exitCode;
if (0 === $exitCode) {
$exitCode = 1;
}
} else {
$exitCode = 1;
}
}
if ($this->autoExit) {
if ($exitCode > 255) {
$exitCode = 255;
}
exit($exitCode);
}
return $exitCode;
}
/**
* Runs the current application.
*
* @param InputInterface $input An Input instance
* @param OutputInterface $output An Output instance
*
* @return int 0 if everything went fine, or an error code
*/
public function doRun(InputInterface $input, OutputInterface $output)
{
if (true === $input->hasParameterOption(array('--version', '-V'), true)) {
$output->writeln($this->getLongVersion());
return 0;
}
$name = $this->getCommandName($input);
if (true === $input->hasParameterOption(array('--help', '-h'), true)) {
if (!$name) {
$name = 'help';
$input = new ArrayInput(array('command' => 'help'));
} else {
$this->wantHelps = true;
}
}
if (!$name) {
$name = $this->defaultCommand;
$input = new ArrayInput(array('command' => $this->defaultCommand));
}
// the command name MUST be the first element of the input
$command = $this->find($name);
$this->runningCommand = $command;
$exitCode = $this->doRunCommand($command, $input, $output);
$this->runningCommand = null;
return $exitCode;
}
/**
* Set a helper set to be used with the command.
*
* @param HelperSet $helperSet The helper set
*/
public function setHelperSet(HelperSet $helperSet)
{
$this->helperSet = $helperSet;
}
/**
* Get the helper set associated with the command.
*
* @return HelperSet The HelperSet instance associated with this command
*/
public function getHelperSet()
{
return $this->helperSet;
}
/**
* Set an input definition to be used with this application.
*
* @param InputDefinition $definition The input definition
*/
public function setDefinition(InputDefinition $definition)
{
$this->definition = $definition;
}
/**
* Gets the InputDefinition related to this Application.
*
* @return InputDefinition The InputDefinition instance
*/
public function getDefinition()
{
return $this->definition;
}
/**
* Gets the help message.
*
* @return string A help message
*/
public function getHelp()
{
return $this->getLongVersion();
}
/**
* Gets whether to catch exceptions or not during commands execution.
*
* @return bool Whether to catch exceptions or not during commands execution
*/
public function areExceptionsCaught()
{
return $this->catchExceptions;
}
/**
* Sets whether to catch exceptions or not during commands execution.
*
* @param bool $boolean Whether to catch exceptions or not during commands execution
*/
public function setCatchExceptions($boolean)
{
$this->catchExceptions = (bool) $boolean;
}
/**
* Gets whether to automatically exit after a command execution or not.
*
* @return bool Whether to automatically exit after a command execution or not
*/
public function isAutoExitEnabled()
{
return $this->autoExit;
}
/**
* Sets whether to automatically exit after a command execution or not.
*
* @param bool $boolean Whether to automatically exit after a command execution or not
*/
public function setAutoExit($boolean)
{
$this->autoExit = (bool) $boolean;
}
/**
* Gets the name of the application.
*
* @return string The application name
*/
public function getName()
{
return $this->name;
}
/**
* Sets the application name.
*
* @param string $name The application name
*/
public function setName($name)
{
$this->name = $name;
}
/**
* Gets the application version.
*
* @return string The application version
*/
public function getVersion()
{
return $this->version;
}
/**
* Sets the application version.
*
* @param string $version The application version
*/
public function setVersion($version)
{
$this->version = $version;
}
/**
* Returns the long version of the application.
*
* @return string The long application version
*/
public function getLongVersion()
{
if ('UNKNOWN' !== $this->getName()) {
if ('UNKNOWN' !== $this->getVersion()) {
return sprintf('<info>%s</info> version <comment>%s</comment>', $this->getName(), $this->getVersion());
}
return sprintf('<info>%s</info>', $this->getName());
}
return '<info>Console Tool</info>';
}
/**
* Registers a new command.
*
* @param string $name The command name
*
* @return Command The newly created command
*/
public function register($name)
{
return $this->add(new Command($name));
}
/**
* Adds an array of command objects.
*
* If a Command is not enabled it will not be added.
*
* @param Command[] $commands An array of commands
*/
public function addCommands(array $commands)
{
foreach ($commands as $command) {
$this->add($command);
}
}
/**
* Adds a command object.
*
* If a command with the same name already exists, it will be overridden.
* If the command is not enabled it will not be added.
*
* @param Command $command A Command object
*
* @return Command|null The registered command if enabled or null
*/
public function add(Command $command)
{
$command->setApplication($this);
if (!$command->isEnabled()) {
$command->setApplication(null);
return;
}
if (null === $command->getDefinition()) {
throw new LogicException(sprintf('Command class "%s" is not correctly initialized. You probably forgot to call the parent constructor.', get_class($command)));
}
$this->commands[$command->getName()] = $command;
foreach ($command->getAliases() as $alias) {
$this->commands[$alias] = $command;
}
return $command;
}
/**
* Returns a registered command by name or alias.
*
* @param string $name The command name or alias
*
* @return Command A Command object
*
* @throws CommandNotFoundException When command name given does not exist
*/
public function get($name)
{
if (!isset($this->commands[$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;
}
/**
* Returns true if the command exists, false otherwise.
*
* @param string $name The command name or alias
*
* @return bool true if the command exists, false otherwise
*/
public function has($name)
{
return isset($this->commands[$name]);
}
/**
* Returns an array of all unique namespaces used by currently registered commands.
*
* It does not return the global namespace which always exists.
*
* @return string[] An array of namespaces
*/
public function getNamespaces()
{
$namespaces = array();
foreach ($this->all() as $command) {
$namespaces = array_merge($namespaces, $this->extractAllNamespaces($command->getName()));
foreach ($command->getAliases() as $alias) {
$namespaces = array_merge($namespaces, $this->extractAllNamespaces($alias));
}
}
return array_values(array_unique(array_filter($namespaces)));
}
/**
* Finds a registered namespace by a name or an abbreviation.
*
* @param string $namespace A namespace or abbreviation to search for
*
* @return string A registered namespace
*
* @throws CommandNotFoundException When namespace is incorrect or ambiguous
*/
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 (%s).', $namespace, $this->getAbbreviationSuggestions(array_values($namespaces))), array_values($namespaces));
}
return $exact ? $namespace : reset($namespaces);
}
/**
* Finds a command by name or alias.
*
* Contrary to get, this command tries to find the best
* match if you give it an abbreviation of a name or alias.
*
* @param string $name A command name or a command alias
*
* @return Command A Command instance
*
* @throws CommandNotFoundException When command name is incorrect or ambiguous
*/
public function find($name)
{
$allCommands = array_keys($this->commands);
$expr = preg_replace_callback('{([^:]+|)}', function ($matches) { return preg_quote($matches[1]).'[^:]*'; }, $name);
$commands = preg_grep('{^'.$expr.'}', $allCommands);
if (empty($commands) || count(preg_grep('{^'.$expr.'$}', $commands)) < 1) {
if (false !== $pos = strrpos($name, ':')) {
// check if a namespace exists and contains commands
$this->findNamespace(substr($name, 0, $pos));
}
$message = sprintf('Command "%s" is not defined.', $name);
if ($alternatives = $this->findAlternatives($name, $allCommands)) {
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);
}
// filter out aliases for commands which are already on the list
if (count($commands) > 1) {
$commandList = $this->commands;
$commands = array_filter($commands, function ($nameOrAlias) use ($commandList, $commands) {
$commandName = $commandList[$nameOrAlias]->getName();
return $commandName === $nameOrAlias || !in_array($commandName, $commands);
});
}
$exact = in_array($name, $commands, true);
if (count($commands) > 1 && !$exact) {
$suggestions = $this->getAbbreviationSuggestions(array_values($commands));
throw new CommandNotFoundException(sprintf('Command "%s" is ambiguous (%s).', $name, $suggestions), array_values($commands));
}
return $this->get($exact ? $name : reset($commands));
}
/**
* Gets the commands (registered in the given namespace if provided).
*
* The array keys are the full names and the values the command instances.
*
* @param string $namespace A namespace name
*
* @return Command[] An array of Command instances
*/
public function all($namespace = null)
{
if (null === $namespace) {
return $this->commands;
}
$commands = array();
foreach ($this->commands as $name => $command) {
if ($namespace === $this->extractNamespace($name, substr_count($namespace, ':') + 1)) {
$commands[$name] = $command;
}
}
return $commands;
}
/**
* Returns an array of possible abbreviations given a set of names.
*
* @param array $names An array of names
*
* @return array An array of abbreviations
*/
public static function getAbbreviations($names)
{
$abbrevs = array();
foreach ($names as $name) {
for ($len = strlen($name); $len > 0; --$len) {
$abbrev = substr($name, 0, $len);
$abbrevs[$abbrev][] = $name;
}
}
return $abbrevs;
}
/**
* Renders a caught exception.
*
* @param \Exception $e An exception instance
* @param OutputInterface $output An OutputInterface instance
*/
public function renderException(\Exception $e, OutputInterface $output)
{
$output->writeln('', OutputInterface::VERBOSITY_QUIET);
do {
$title = sprintf(
' [%s%s] ',
get_class($e),
$output->isVerbose() && 0 !== ($code = $e->getCode()) ? ' ('.$code.')' : ''
);
$len = $this->stringWidth($title);
$width = $this->getTerminalWidth() ? $this->getTerminalWidth() - 1 : PHP_INT_MAX;
// HHVM only accepts 32 bits integer in str_split, even when PHP_INT_MAX is a 64 bit integer: https://github.com/facebook/hhvm/issues/1327
if (defined('HHVM_VERSION') && $width > 1 << 31) {
$width = 1 << 31;
}
$formatter = $output->getFormatter();
$lines = array();
foreach (preg_split('/\r?\n/', $e->getMessage()) as $line) {
foreach ($this->splitStringByWidth($line, $width - 4) as $line) {
// pre-format lines to get the right string length
$lineLength = $this->stringWidth(preg_replace('/\[[^m]*m/', '', $formatter->format($line))) + 4;
$lines[] = array($line, $lineLength);
$len = max($lineLength, $len);
}
}
$messages = array();
$messages[] = $emptyLine = $formatter->format(sprintf('<error>%s</error>', str_repeat(' ', $len)));
$messages[] = $formatter->format(sprintf('<error>%s%s</error>', $title, str_repeat(' ', max(0, $len - $this->stringWidth($title)))));
foreach ($lines as $line) {
$messages[] = $formatter->format(sprintf('<error> %s %s</error>', $line[0], str_repeat(' ', $len - $line[1])));
}
$messages[] = $emptyLine;
$messages[] = '';
$output->writeln($messages, OutputInterface::OUTPUT_RAW | OutputInterface::VERBOSITY_QUIET);
if (OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) {
$output->writeln('<comment>Exception trace:</comment>', OutputInterface::VERBOSITY_QUIET);
// exception related properties
$trace = $e->getTrace();
array_unshift($trace, array(
'function' => '',
'file' => $e->getFile() !== null ? $e->getFile() : 'n/a',
'line' => $e->getLine() !== null ? $e->getLine() : 'n/a',
'args' => array(),
));
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 = $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%s() at <info>%s:%s</info>', $class, $type, $function, $file, $line), OutputInterface::VERBOSITY_QUIET);
}
$output->writeln('', OutputInterface::VERBOSITY_QUIET);
}
} while ($e = $e->getPrevious());
if (null !== $this->runningCommand) {
$output->writeln(sprintf('<info>%s</info>', sprintf($this->runningCommand->getSynopsis(), $this->getName())), OutputInterface::VERBOSITY_QUIET);
$output->writeln('', OutputInterface::VERBOSITY_QUIET);
}
}
/**
* Tries to figure out the terminal width in which this application runs.
*
* @return int|null
*/
protected function getTerminalWidth()
{
$dimensions = $this->getTerminalDimensions();
return $dimensions[0];
}
/**
* Tries to figure out the terminal height in which this application runs.
*
* @return int|null
*/
protected function getTerminalHeight()
{
$dimensions = $this->getTerminalDimensions();
return $dimensions[1];
}
/**
* Tries to figure out the terminal dimensions based on the current environment.
*
* @return array Array containing width and height
*/
public function getTerminalDimensions()
{
if ($this->terminalDimensions) {
return $this->terminalDimensions;
}
if ('\\' === DIRECTORY_SEPARATOR) {
// extract [w, H] from "wxh (WxH)"
if (preg_match('/^(\d+)x\d+ \(\d+x(\d+)\)$/', trim(getenv('ANSICON')), $matches)) {
return array((int) $matches[1], (int) $matches[2]);
}
// extract [w, h] from "wxh"
if (preg_match('/^(\d+)x(\d+)$/', $this->getConsoleMode(), $matches)) {
return array((int) $matches[1], (int) $matches[2]);
}
}
if ($sttyString = $this->getSttyColumns()) {
// extract [w, h] from "rows h; columns w;"
if (preg_match('/rows.(\d+);.columns.(\d+);/i', $sttyString, $matches)) {
return array((int) $matches[2], (int) $matches[1]);
}
// extract [w, h] from "; h rows; w columns"
if (preg_match('/;.(\d+).rows;.(\d+).columns/i', $sttyString, $matches)) {
return array((int) $matches[2], (int) $matches[1]);
}
}
return array(null, null);
}
/**
* Sets terminal dimensions.
*
* Can be useful to force terminal dimensions for functional tests.
*
* @param int $width The width
* @param int $height The height
*
* @return Application The current application
*/
public function setTerminalDimensions($width, $height)
{
$this->terminalDimensions = array($width, $height);
return $this;
}
/**
* Configures the input and output instances based on the user arguments and options.
*
* @param InputInterface $input An InputInterface instance
* @param OutputInterface $output An OutputInterface instance
*/
protected function configureIO(InputInterface $input, OutputInterface $output)
{
if (true === $input->hasParameterOption(array('--ansi'), true)) {
$output->setDecorated(true);
} elseif (true === $input->hasParameterOption(array('--no-ansi'), true)) {
$output->setDecorated(false);
}
if (true === $input->hasParameterOption(array('--no-interaction', '-n'), true)) {
$input->setInteractive(false);
} elseif (function_exists('posix_isatty') && $this->getHelperSet()->has('question')) {
$inputStream = $this->getHelperSet()->get('question')->getInputStream();
if (!@posix_isatty($inputStream) && false === getenv('SHELL_INTERACTIVE')) {
$input->setInteractive(false);
}
}
if (true === $input->hasParameterOption(array('--quiet', '-q'), true)) {
$output->setVerbosity(OutputInterface::VERBOSITY_QUIET);
$input->setInteractive(false);
} else {
if ($input->hasParameterOption('-vvv', true) || $input->hasParameterOption('--verbose=3', true) || $input->getParameterOption('--verbose', false, true) === 3) {
$output->setVerbosity(OutputInterface::VERBOSITY_DEBUG);
} elseif ($input->hasParameterOption('-vv', true) || $input->hasParameterOption('--verbose=2', true) || $input->getParameterOption('--verbose', false, true) === 2) {
$output->setVerbosity(OutputInterface::VERBOSITY_VERY_VERBOSE);
} elseif ($input->hasParameterOption('-v', true) || $input->hasParameterOption('--verbose=1', true) || $input->hasParameterOption('--verbose', true) || $input->getParameterOption('--verbose', false, true)) {
$output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE);
}
}
}
/**
* Runs the current command.
*
* If an event dispatcher has been attached to the application,
* events are also dispatched during the life-cycle of the command.
*
* @param Command $command A Command instance
* @param InputInterface $input An Input instance
* @param OutputInterface $output An Output instance
*
* @return int 0 if everything went fine, or an error code
*
* @throws \Exception when the command being run threw an exception
*/
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);
}
// bind before the console.command event, so the listeners have access to input options/arguments
try {
$command->mergeApplicationDefinition();
$input->bind($command->getDefinition());
} catch (ExceptionInterface $e) {
// ignore invalid options/arguments for now, to allow the event listeners to customize the InputDefinition
}
$event = new ConsoleCommandEvent($command, $input, $output);
$this->dispatcher->dispatch(ConsoleEvents::COMMAND, $event);
if ($event->commandShouldRun()) {
try {
$e = null;
$exitCode = $command->run($input, $output);
} catch (\Exception $x) {
$e = $x;
} catch (\Throwable $x) {
$e = new FatalThrowableError($x);
}
if (null !== $e) {
$event = new ConsoleExceptionEvent($command, $input, $output, $e, $e->getCode());
$this->dispatcher->dispatch(ConsoleEvents::EXCEPTION, $event);
if ($e !== $event->getException()) {
$x = $e = $event->getException();
}
$event = new ConsoleTerminateEvent($command, $input, $output, $e->getCode());
$this->dispatcher->dispatch(ConsoleEvents::TERMINATE, $event);
throw $x;
}
} else {
$exitCode = ConsoleCommandEvent::RETURN_CODE_DISABLED;
}
$event = new ConsoleTerminateEvent($command, $input, $output, $exitCode);
$this->dispatcher->dispatch(ConsoleEvents::TERMINATE, $event);
return $event->getExitCode();
}
/**
* Gets the name of the command based on input.
*
* @param InputInterface $input The input interface
*
* @return string The command name
*/
protected function getCommandName(InputInterface $input)
{
return $input->getFirstArgument();
}
/**
* Gets the default input definition.
*
* @return InputDefinition An InputDefinition instance
*/
protected function getDefaultInputDefinition()
{
return new InputDefinition(array(
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'),
));
}
/**
* Gets the default commands that should always be available.
*
* @return Command[] An array of default Command instances
*/
protected function getDefaultCommands()
{
return array(new HelpCommand(), new ListCommand());
}
/**
* Gets the default helper set with the helpers that should always be available.
*
* @return HelperSet A HelperSet instance
*/
protected function getDefaultHelperSet()
{
return new HelperSet(array(
new FormatterHelper(),
new DebugFormatterHelper(),
new ProcessHelper(),
new QuestionHelper(),
));
}
/**
* Runs and parses stty -a if it's available, suppressing any error output.
*
* @return string
*/
private function getSttyColumns()
{
if (!function_exists('proc_open')) {
return;
}
$descriptorspec = array(1 => array('pipe', 'w'), 2 => array('pipe', 'w'));
$process = proc_open('stty -a | grep columns', $descriptorspec, $pipes, null, null, array('suppress_errors' => true));
if (is_resource($process)) {
$info = stream_get_contents($pipes[1]);
fclose($pipes[1]);
fclose($pipes[2]);
proc_close($process);
return $info;
}
}
/**
* Runs and parses mode CON if it's available, suppressing any error output.
*
* @return string|null <width>x<height> or null if it could not be parsed
*/
private function getConsoleMode()
{
if (!function_exists('proc_open')) {
return;
}
$descriptorspec = array(1 => array('pipe', 'w'), 2 => array('pipe', 'w'));
$process = proc_open('mode CON', $descriptorspec, $pipes, null, null, array('suppress_errors' => true));
if (is_resource($process)) {
$info = stream_get_contents($pipes[1]);
fclose($pipes[1]);
fclose($pipes[2]);
proc_close($process);
if (preg_match('/--------+\r?\n.+?(\d+)\r?\n.+?(\d+)\r?\n/', $info, $matches)) {
return $matches[2].'x'.$matches[1];
}
}
}
/**
* Returns abbreviated suggestions in string format.
*
* @param array $abbrevs Abbreviated suggestions to convert
*
* @return string A formatted string of abbreviated suggestions
*/
private function getAbbreviationSuggestions($abbrevs)
{
return sprintf('%s, %s%s', $abbrevs[0], $abbrevs[1], count($abbrevs) > 2 ? sprintf(' and %d more', count($abbrevs) - 2) : '');
}
/**
* Returns the namespace part of the command name.
*
* This method is not part of public API and should not be used directly.
*
* @param string $name The full name of the command
* @param string $limit The maximum number of parts of the namespace
*
* @return string The namespace of the command
*/
public function extractNamespace($name, $limit = null)
{
$parts = explode(':', $name);
array_pop($parts);
return implode(':', null === $limit ? $parts : array_slice($parts, 0, $limit));
}
/**
* Finds alternative of $name among $collection,
* if nothing is found in $collection, try in $abbrevs.
*
* @param string $name The string
* @param array|\Traversable $collection The collection
*
* @return string[] A sorted array of similar string
*/
private function findAlternatives($name, $collection)
{
$threshold = 1e3;
$alternatives = array();
$collectionParts = array();
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; });
asort($alternatives);
return array_keys($alternatives);
}
/**
* Sets the default Command name.
*
* @param string $commandName The Command name
*/
public function setDefaultCommand($commandName)
{
$this->defaultCommand = $commandName;
}
private function stringWidth($string)
{
if (false === $encoding = mb_detect_encoding($string, null, true)) {
return strlen($string);
}
return mb_strwidth($string, $encoding);
}
private function splitStringByWidth($string, $width)
{
// str_split is not suitable for multi-byte characters, we should use preg_split to get char array properly.
// additionally, array_slice() is not enough as some character has doubled width.
// we need a function to split string not by character count but by string width
if (false === $encoding = mb_detect_encoding($string, null, true)) {
return str_split($string, $width);
}
$utf8String = mb_convert_encoding($string, 'utf8', $encoding);
$lines = array();
$line = '';
foreach (preg_split('//u', $utf8String) as $char) {
// test if $char could be appended to current line
if (mb_strwidth($line.$char, 'utf8') <= $width) {
$line .= $char;
continue;
}
// if not, push current line to array and make new line
$lines[] = str_pad($line, $width);
$line = $char;
}
if ('' !== $line) {
$lines[] = count($lines) ? str_pad($line, $width) : $line;
}
mb_convert_variables($encoding, 'utf8', $lines);
return $lines;
}
/**
* Returns all namespaces of the command name.
*
* @param string $name The full name of the command
*
* @return string[] The namespaces of the command
*/
private function extractAllNamespaces($name)
{
// -1 as third argument is needed to skip the command short name when exploding
$parts = explode(':', $name, -1);
$namespaces = array();
foreach ($parts as $part) {
if (count($namespaces)) {
$namespaces[] = end($namespaces).':'.$part;
} else {
$namespaces[] = $part;
}
}
return $namespaces;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Command;
use Symfony\Component\Console\Exception\ExceptionInterface;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Helper\HelperSet;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\LogicException;
/**
* Base class for all commands.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class Command
{
private $application;
private $name;
private $processTitle;
private $aliases = array();
private $definition;
private $help;
private $description;
private $ignoreValidationErrors = false;
private $applicationDefinitionMerged = false;
private $applicationDefinitionMergedWithArgs = false;
private $code;
private $synopsis = array();
private $usages = array();
private $helperSet;
/**
* Constructor.
*
* @param string|null $name The name of the command; passing null means it must be set in configure()
*
* @throws LogicException When the command name is empty
*/
public function __construct($name = null)
{
$this->definition = new InputDefinition();
if (null !== $name) {
$this->setName($name);
}
$this->configure();
if (!$this->name) {
throw new LogicException(sprintf('The command defined in "%s" cannot have an empty name.', get_class($this)));
}
}
/**
* Ignores validation errors.
*
* This is mainly useful for the help command.
*/
public function ignoreValidationErrors()
{
$this->ignoreValidationErrors = true;
}
/**
* Sets the application instance for this command.
*
* @param Application $application An Application instance
*/
public function setApplication(Application $application = null)
{
$this->application = $application;
if ($application) {
$this->setHelperSet($application->getHelperSet());
} else {
$this->helperSet = null;
}
}
/**
* Sets the helper set.
*
* @param HelperSet $helperSet A HelperSet instance
*/
public function setHelperSet(HelperSet $helperSet)
{
$this->helperSet = $helperSet;
}
/**
* Gets the helper set.
*
* @return HelperSet A HelperSet instance
*/
public function getHelperSet()
{
return $this->helperSet;
}
/**
* Gets the application instance for this command.
*
* @return Application An Application instance
*/
public function getApplication()
{
return $this->application;
}
/**
* Checks whether the command is enabled or not in the current environment.
*
* Override this to check for x or y and return false if the command can not
* run properly under the current conditions.
*
* @return bool
*/
public function isEnabled()
{
return true;
}
/**
* Configures the current command.
*/
protected function configure()
{
}
/**
* Executes the current command.
*
* This method is not abstract because you can use this class
* as a concrete class. In this case, instead of defining the
* execute() method, you set the code to execute by passing
* a Closure to the setCode() method.
*
* @param InputInterface $input An InputInterface instance
* @param OutputInterface $output An OutputInterface instance
*
* @return null|int null or 0 if everything went fine, or an error code
*
* @throws LogicException When this abstract method is not implemented
*
* @see setCode()
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
throw new LogicException('You must override the execute() method in the concrete command class.');
}
/**
* Interacts with the user.
*
* This method is executed before the InputDefinition is validated.
* This means that this is the only place where the command can
* interactively ask for values of missing required arguments.
*
* @param InputInterface $input An InputInterface instance
* @param OutputInterface $output An OutputInterface instance
*/
protected function interact(InputInterface $input, OutputInterface $output)
{
}
/**
* Initializes the command just after the input has been validated.
*
* This is mainly useful when a lot of commands extends one main command
* where some things need to be initialized based on the input arguments and options.
*
* @param InputInterface $input An InputInterface instance
* @param OutputInterface $output An OutputInterface instance
*/
protected function initialize(InputInterface $input, OutputInterface $output)
{
}
/**
* Runs the command.
*
* The code to execute is either defined directly with the
* setCode() method or by overriding the execute() method
* in a sub-class.
*
* @param InputInterface $input An InputInterface instance
* @param OutputInterface $output An OutputInterface instance
*
* @return int The command exit code
*
* @throws \Exception
*
* @see setCode()
* @see execute()
*/
public function run(InputInterface $input, OutputInterface $output)
{
// force the creation of the synopsis before the merge with the app definition
$this->getSynopsis(true);
$this->getSynopsis(false);
// add the application arguments and options
$this->mergeApplicationDefinition();
// bind the input against the command specific arguments/options
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')) {
cli_set_process_title($this->processTitle);
} elseif (function_exists('setproctitle')) {
setproctitle($this->processTitle);
} elseif (OutputInterface::VERBOSITY_VERY_VERBOSE === $output->getVerbosity()) {
$output->writeln('<comment>Install the proctitle PECL to be able to change the process title.</comment>');
}
}
if ($input->isInteractive()) {
$this->interact($input, $output);
}
// The command name argument is often omitted when a command is executed directly with its run() method.
// It would fail the validation if we didn't make sure the command argument is present,
// since it's required by the application.
if ($input->hasArgument('command') && null === $input->getArgument('command')) {
$input->setArgument('command', $this->getName());
}
$input->validate();
if ($this->code) {
$statusCode = call_user_func($this->code, $input, $output);
} else {
$statusCode = $this->execute($input, $output);
}
return is_numeric($statusCode) ? (int) $statusCode : 0;
}
/**
* Sets the code to execute when running this command.
*
* If this method is used, it overrides the code defined
* in the execute() method.
*
* @param callable $code A callable(InputInterface $input, OutputInterface $output)
*
* @return Command The current instance
*
* @throws InvalidArgumentException
*
* @see execute()
*/
public function setCode(callable $code)
{
if ($code instanceof \Closure) {
$r = new \ReflectionFunction($code);
if (null === $r->getClosureThis()) {
$code = \Closure::bind($code, $this);
}
}
$this->code = $code;
return $this;
}
/**
* Merges the application definition with the command definition.
*
* This method is not part of public API and should not be used directly.
*
* @param bool $mergeArgs Whether to merge or not the Application definition arguments to Command definition arguments
*/
public function mergeApplicationDefinition($mergeArgs = true)
{
if (null === $this->application || (true === $this->applicationDefinitionMerged && ($this->applicationDefinitionMergedWithArgs || !$mergeArgs))) {
return;
}
$this->definition->addOptions($this->application->getDefinition()->getOptions());
if ($mergeArgs) {
$currentArguments = $this->definition->getArguments();
$this->definition->setArguments($this->application->getDefinition()->getArguments());
$this->definition->addArguments($currentArguments);
}
$this->applicationDefinitionMerged = true;
if ($mergeArgs) {
$this->applicationDefinitionMergedWithArgs = true;
}
}
/**
* Sets an array of argument and option instances.
*
* @param array|InputDefinition $definition An array of argument and option instances or a definition instance
*
* @return Command The current instance
*/
public function setDefinition($definition)
{
if ($definition instanceof InputDefinition) {
$this->definition = $definition;
} else {
$this->definition->setDefinition($definition);
}
$this->applicationDefinitionMerged = false;
return $this;
}
/**
* Gets the InputDefinition attached to this Command.
*
* @return InputDefinition An InputDefinition instance
*/
public function getDefinition()
{
return $this->definition;
}
/**
* Gets the InputDefinition to be used to create representations of this Command.
*
* Can be overridden to provide the original command representation when it would otherwise
* be changed by merging with the application InputDefinition.
*
* This method is not part of public API and should not be used directly.
*
* @return InputDefinition An InputDefinition instance
*/
public function getNativeDefinition()
{
return $this->getDefinition();
}
/**
* Adds an argument.
*
* @param string $name The argument name
* @param int $mode The argument mode: InputArgument::REQUIRED or InputArgument::OPTIONAL
* @param string $description A description text
* @param mixed $default The default value (for InputArgument::OPTIONAL mode only)
*
* @return Command The current instance
*/
public function addArgument($name, $mode = null, $description = '', $default = null)
{
$this->definition->addArgument(new InputArgument($name, $mode, $description, $default));
return $this;
}
/**
* Adds an option.
*
* @param string $name The option name
* @param string $shortcut The shortcut (can be null)
* @param int $mode The option mode: One of the InputOption::VALUE_* constants
* @param string $description A description text
* @param mixed $default The default value (must be null for InputOption::VALUE_NONE)
*
* @return Command The current instance
*/
public function addOption($name, $shortcut = null, $mode = null, $description = '', $default = null)
{
$this->definition->addOption(new InputOption($name, $shortcut, $mode, $description, $default));
return $this;
}
/**
* Sets the name of the command.
*
* This method can set both the namespace and the name if
* you separate them by a colon (:)
*
* $command->setName('foo:bar');
*
* @param string $name The command name
*
* @return Command The current instance
*
* @throws InvalidArgumentException When the name is invalid
*/
public function setName($name)
{
$this->validateName($name);
$this->name = $name;
return $this;
}
/**
* Sets the process title of the command.
*
* This feature should be used only when creating a long process command,
* like a daemon.
*
* PHP 5.5+ or the proctitle PECL library is required
*
* @param string $title The process title
*
* @return Command The current instance
*/
public function setProcessTitle($title)
{
$this->processTitle = $title;
return $this;
}
/**
* Returns the command name.
*
* @return string The command name
*/
public function getName()
{
return $this->name;
}
/**
* Sets the description for the command.
*
* @param string $description The description for the command
*
* @return Command The current instance
*/
public function setDescription($description)
{
$this->description = $description;
return $this;
}
/**
* Returns the description for the command.
*
* @return string The description for the command
*/
public function getDescription()
{
return $this->description;
}
/**
* Sets the help for the command.
*
* @param string $help The help for the command
*
* @return Command The current instance
*/
public function setHelp($help)
{
$this->help = $help;
return $this;
}
/**
* Returns the help for the command.
*
* @return string The help for the command
*/
public function getHelp()
{
return $this->help;
}
/**
* Returns the processed help for the command replacing the %command.name% and
* %command.full_name% patterns with the real values dynamically.
*
* @return string The processed help for the command
*/
public function getProcessedHelp()
{
$name = $this->name;
$placeholders = array(
'%command.name%',
'%command.full_name%',
);
$replacements = array(
$name,
$_SERVER['PHP_SELF'].' '.$name,
);
return str_replace($placeholders, $replacements, $this->getHelp() ?: $this->getDescription());
}
/**
* Sets the aliases for the command.
*
* @param string[] $aliases An array of aliases for the command
*
* @return Command The current instance
*
* @throws InvalidArgumentException When an alias is invalid
*/
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;
}
/**
* Returns the aliases for the command.
*
* @return array An array of aliases for the command
*/
public function getAliases()
{
return $this->aliases;
}
/**
* Returns the synopsis for the command.
*
* @param bool $short Whether to show the short version of the synopsis (with options folded) or not
*
* @return string The synopsis
*/
public function getSynopsis($short = false)
{
$key = $short ? 'short' : 'long';
if (!isset($this->synopsis[$key])) {
$this->synopsis[$key] = trim(sprintf('%s %s', $this->name, $this->definition->getSynopsis($short)));
}
return $this->synopsis[$key];
}
/**
* Add a command usage example.
*
* @param string $usage The usage, it'll be prefixed with the command name
*
* @return Command The current instance
*/
public function addUsage($usage)
{
if (0 !== strpos($usage, $this->name)) {
$usage = sprintf('%s %s', $this->name, $usage);
}
$this->usages[] = $usage;
return $this;
}
/**
* Returns alternative usages of the command.
*
* @return array
*/
public function getUsages()
{
return $this->usages;
}
/**
* Gets a helper instance by name.
*
* @param string $name The helper name
*
* @return mixed The helper value
*
* @throws LogicException if no HelperSet is defined
* @throws InvalidArgumentException if the helper is not defined
*/
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);
}
/**
* Validates a command name.
*
* It must be non-empty and parts can optionally be separated by ":".
*
* @param string $name
*
* @throws InvalidArgumentException When the name is invalid
*/
private function validateName($name)
{
if (!preg_match('/^[^\:]++(\:[^\:]++)*$/', $name)) {
throw new InvalidArgumentException(sprintf('Command name "%s" is invalid.', $name));
}
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Command;
use Symfony\Component\Console\Helper\DescriptorHelper;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* HelpCommand displays the help for a given command.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class HelpCommand extends Command
{
private $command;
/**
* {@inheritdoc}
*/
protected function configure()
{
$this->ignoreValidationErrors();
$this
->setName('help')
->setDefinition(array(
new InputArgument('command_name', InputArgument::OPTIONAL, 'The command name', 'help'),
new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt'),
new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command help'),
))
->setDescription('Displays help for a command')
->setHelp(<<<'EOF'
The <info>%command.name%</info> command displays help for a given command:
<info>php %command.full_name% list</info>
You can also output the help in other formats by using the <comment>--format</comment> option:
<info>php %command.full_name% --format=xml list</info>
To display the list of available commands, please use the <info>list</info> command.
EOF
)
;
}
/**
* Sets the command.
*
* @param Command $command The command to set
*/
public function setCommand(Command $command)
{
$this->command = $command;
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
if (null === $this->command) {
$this->command = $this->getApplication()->find($input->getArgument('command_name'));
}
$helper = new DescriptorHelper();
$helper->describe($output, $this->command, array(
'format' => $input->getOption('format'),
'raw_text' => $input->getOption('raw'),
));
$this->command = null;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Command;
use Symfony\Component\Console\Helper\DescriptorHelper;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputDefinition;
/**
* ListCommand displays the list of all available commands for the application.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class ListCommand extends Command
{
/**
* {@inheritdoc}
*/
protected function configure()
{
$this
->setName('list')
->setDefinition($this->createDefinition())
->setDescription('Lists commands')
->setHelp(<<<'EOF'
The <info>%command.name%</info> command lists all commands:
<info>php %command.full_name%</info>
You can also display the commands for a specific namespace:
<info>php %command.full_name% test</info>
You can also output the information in other formats by using the <comment>--format</comment> option:
<info>php %command.full_name% --format=xml</info>
It's also possible to get raw list of commands (useful for embedding command runner):
<info>php %command.full_name% --raw</info>
EOF
)
;
}
/**
* {@inheritdoc}
*/
public function getNativeDefinition()
{
return $this->createDefinition();
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$helper = new DescriptorHelper();
$helper->describe($output, $this->getApplication(), array(
'format' => $input->getOption('format'),
'raw_text' => $input->getOption('raw'),
'namespace' => $input->getArgument('namespace'),
));
}
/**
* {@inheritdoc}
*/
private function createDefinition()
{
return new InputDefinition(array(
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'),
));
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console;
/**
* Contains all events dispatched by an Application.
*
* @author Francesco Levorato <git@flevour.net>
*/
final class ConsoleEvents
{
/**
* The COMMAND event allows you to attach listeners before any command is
* executed by the console. It also allows you to modify the command, input and output
* before they are handled to the command.
*
* @Event("Symfony\Component\Console\Event\ConsoleCommandEvent")
*
* @var string
*/
const COMMAND = 'console.command';
/**
* The TERMINATE event allows you to attach listeners after a command is
* executed by the console.
*
* @Event("Symfony\Component\Console\Event\ConsoleTerminateEvent")
*
* @var string
*/
const TERMINATE = 'console.terminate';
/**
* The EXCEPTION event occurs when an uncaught exception appears.
*
* This event allows you to deal with the exception or
* to modify the thrown exception.
*
* @Event("Symfony\Component\Console\Event\ConsoleExceptionEvent")
*
* @var string
*/
const EXCEPTION = 'console.exception';
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Descriptor;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\CommandNotFoundException;
/**
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
*
* @internal
*/
class ApplicationDescription
{
const GLOBAL_NAMESPACE = '_global';
/**
* @var Application
*/
private $application;
/**
* @var null|string
*/
private $namespace;
/**
* @var array
*/
private $namespaces;
/**
* @var Command[]
*/
private $commands;
/**
* @var Command[]
*/
private $aliases;
/**
* Constructor.
*
* @param Application $application
* @param string|null $namespace
*/
public function __construct(Application $application, $namespace = null)
{
$this->application = $application;
$this->namespace = $namespace;
}
/**
* @return array
*/
public function getNamespaces()
{
if (null === $this->namespaces) {
$this->inspectApplication();
}
return $this->namespaces;
}
/**
* @return Command[]
*/
public function getCommands()
{
if (null === $this->commands) {
$this->inspectApplication();
}
return $this->commands;
}
/**
* @param string $name
*
* @return Command
*
* @throws CommandNotFoundException
*/
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 = array();
$this->namespaces = array();
$all = $this->application->all($this->namespace ? $this->application->findNamespace($this->namespace) : null);
foreach ($this->sortCommands($all) as $namespace => $commands) {
$names = array();
/** @var Command $command */
foreach ($commands as $name => $command) {
if (!$command->getName()) {
continue;
}
if ($command->getName() === $name) {
$this->commands[$name] = $command;
} else {
$this->aliases[$name] = $command;
}
$names[] = $name;
}
$this->namespaces[$namespace] = array('id' => $namespace, 'commands' => $names);
}
}
/**
* @param array $commands
*
* @return array
*/
private function sortCommands(array $commands)
{
$namespacedCommands = array();
$globalCommands = array();
foreach ($commands as $name => $command) {
$key = $this->application->extractNamespace($name, 1);
if (!$key) {
$globalCommands['_global'][$name] = $command;
} else {
$namespacedCommands[$key][$name] = $command;
}
}
ksort($namespacedCommands);
$namespacedCommands = array_merge($globalCommands, $namespacedCommands);
foreach ($namespacedCommands as &$commandsSet) {
ksort($commandsSet);
}
// unset reference to keep scope clear
unset($commandsSet);
return $namespacedCommands;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Descriptor;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Exception\InvalidArgumentException;
/**
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
*
* @internal
*/
abstract class Descriptor implements DescriptorInterface
{
/**
* @var OutputInterface
*/
protected $output;
/**
* {@inheritdoc}
*/
public function describe(OutputInterface $output, $object, array $options = array())
{
$this->output = $output;
switch (true) {
case $object instanceof InputArgument:
$this->describeInputArgument($object, $options);
break;
case $object instanceof InputOption:
$this->describeInputOption($object, $options);
break;
case $object instanceof InputDefinition:
$this->describeInputDefinition($object, $options);
break;
case $object instanceof Command:
$this->describeCommand($object, $options);
break;
case $object instanceof Application:
$this->describeApplication($object, $options);
break;
default:
throw new InvalidArgumentException(sprintf('Object of type "%s" is not describable.', get_class($object)));
}
}
/**
* Writes content to output.
*
* @param string $content
* @param bool $decorated
*/
protected function write($content, $decorated = false)
{
$this->output->write($content, false, $decorated ? OutputInterface::OUTPUT_NORMAL : OutputInterface::OUTPUT_RAW);
}
/**
* Describes an InputArgument instance.
*
* @param InputArgument $argument
* @param array $options
*
* @return string|mixed
*/
abstract protected function describeInputArgument(InputArgument $argument, array $options = array());
/**
* Describes an InputOption instance.
*
* @param InputOption $option
* @param array $options
*
* @return string|mixed
*/
abstract protected function describeInputOption(InputOption $option, array $options = array());
/**
* Describes an InputDefinition instance.
*
* @param InputDefinition $definition
* @param array $options
*
* @return string|mixed
*/
abstract protected function describeInputDefinition(InputDefinition $definition, array $options = array());
/**
* Describes a Command instance.
*
* @param Command $command
* @param array $options
*
* @return string|mixed
*/
abstract protected function describeCommand(Command $command, array $options = array());
/**
* Describes an Application instance.
*
* @param Application $application
* @param array $options
*
* @return string|mixed
*/
abstract protected function describeApplication(Application $application, array $options = array());
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Descriptor;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Descriptor interface.
*
* @author Jean-François Simon <contact@jfsimon.fr>
*/
interface DescriptorInterface
{
/**
* Describes an InputArgument instance.
*
* @param OutputInterface $output
* @param object $object
* @param array $options
*/
public function describe(OutputInterface $output, $object, array $options = array());
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Descriptor;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;
/**
* JSON descriptor.
*
* @author Jean-François Simon <contact@jfsimon.fr>
*
* @internal
*/
class JsonDescriptor extends Descriptor
{
/**
* {@inheritdoc}
*/
protected function describeInputArgument(InputArgument $argument, array $options = array())
{
$this->writeData($this->getInputArgumentData($argument), $options);
}
/**
* {@inheritdoc}
*/
protected function describeInputOption(InputOption $option, array $options = array())
{
$this->writeData($this->getInputOptionData($option), $options);
}
/**
* {@inheritdoc}
*/
protected function describeInputDefinition(InputDefinition $definition, array $options = array())
{
$this->writeData($this->getInputDefinitionData($definition), $options);
}
/**
* {@inheritdoc}
*/
protected function describeCommand(Command $command, array $options = array())
{
$this->writeData($this->getCommandData($command), $options);
}
/**
* {@inheritdoc}
*/
protected function describeApplication(Application $application, array $options = array())
{
$describedNamespace = isset($options['namespace']) ? $options['namespace'] : null;
$description = new ApplicationDescription($application, $describedNamespace);
$commands = array();
foreach ($description->getCommands() as $command) {
$commands[] = $this->getCommandData($command);
}
$data = $describedNamespace
? array('commands' => $commands, 'namespace' => $describedNamespace)
: array('commands' => $commands, 'namespaces' => array_values($description->getNamespaces()));
$this->writeData($data, $options);
}
/**
* Writes data as json.
*
* @param array $data
* @param array $options
*
* @return array|string
*/
private function writeData(array $data, array $options)
{
$this->write(json_encode($data, isset($options['json_encoding']) ? $options['json_encoding'] : 0));
}
/**
* @param InputArgument $argument
*
* @return array
*/
private function getInputArgumentData(InputArgument $argument)
{
return array(
'name' => $argument->getName(),
'is_required' => $argument->isRequired(),
'is_array' => $argument->isArray(),
'description' => preg_replace('/\s*[\r\n]\s*/', ' ', $argument->getDescription()),
'default' => $argument->getDefault(),
);
}
/**
* @param InputOption $option
*
* @return array
*/
private function getInputOptionData(InputOption $option)
{
return array(
'name' => '--'.$option->getName(),
'shortcut' => $option->getShortcut() ? '-'.implode('|-', explode('|', $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' => $option->getDefault(),
);
}
/**
* @param InputDefinition $definition
*
* @return array
*/
private function getInputDefinitionData(InputDefinition $definition)
{
$inputArguments = array();
foreach ($definition->getArguments() as $name => $argument) {
$inputArguments[$name] = $this->getInputArgumentData($argument);
}
$inputOptions = array();
foreach ($definition->getOptions() as $name => $option) {
$inputOptions[$name] = $this->getInputOptionData($option);
}
return array('arguments' => $inputArguments, 'options' => $inputOptions);
}
/**
* @param Command $command
*
* @return array
*/
private function getCommandData(Command $command)
{
$command->getSynopsis();
$command->mergeApplicationDefinition(false);
return array(
'name' => $command->getName(),
'usage' => array_merge(array($command->getSynopsis()), $command->getUsages(), $command->getAliases()),
'description' => $command->getDescription(),
'help' => $command->getProcessedHelp(),
'definition' => $this->getInputDefinitionData($command->getNativeDefinition()),
);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Descriptor;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;
/**
* Markdown descriptor.
*
* @author Jean-François Simon <contact@jfsimon.fr>
*
* @internal
*/
class MarkdownDescriptor extends Descriptor
{
/**
* {@inheritdoc}
*/
protected function describeInputArgument(InputArgument $argument, array $options = array())
{
$this->write(
'**'.$argument->getName().':**'."\n\n"
.'* Name: '.($argument->getName() ?: '<none>')."\n"
.'* Is required: '.($argument->isRequired() ? 'yes' : 'no')."\n"
.'* Is array: '.($argument->isArray() ? 'yes' : 'no')."\n"
.'* Description: '.preg_replace('/\s*[\r\n]\s*/', "\n ", $argument->getDescription() ?: '<none>')."\n"
.'* Default: `'.str_replace("\n", '', var_export($argument->getDefault(), true)).'`'
);
}
/**
* {@inheritdoc}
*/
protected function describeInputOption(InputOption $option, array $options = array())
{
$this->write(
'**'.$option->getName().':**'."\n\n"
.'* Name: `--'.$option->getName().'`'."\n"
.'* Shortcut: '.($option->getShortcut() ? '`-'.implode('|-', explode('|', $option->getShortcut())).'`' : '<none>')."\n"
.'* Accept value: '.($option->acceptValue() ? 'yes' : 'no')."\n"
.'* Is value required: '.($option->isValueRequired() ? 'yes' : 'no')."\n"
.'* Is multiple: '.($option->isArray() ? 'yes' : 'no')."\n"
.'* Description: '.preg_replace('/\s*[\r\n]\s*/', "\n ", $option->getDescription() ?: '<none>')."\n"
.'* Default: `'.str_replace("\n", '', var_export($option->getDefault(), true)).'`'
);
}
/**
* {@inheritdoc}
*/
protected function describeInputDefinition(InputDefinition $definition, array $options = array())
{
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));
}
}
}
/**
* {@inheritdoc}
*/
protected function describeCommand(Command $command, array $options = array())
{
$command->getSynopsis();
$command->mergeApplicationDefinition(false);
$this->write(
$command->getName()."\n"
.str_repeat('-', strlen($command->getName()))."\n\n"
.'* Description: '.($command->getDescription() ?: '<none>')."\n"
.'* Usage:'."\n\n"
.array_reduce(array_merge(array($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());
}
}
/**
* {@inheritdoc}
*/
protected function describeApplication(Application $application, array $options = array())
{
$describedNamespace = isset($options['namespace']) ? $options['namespace'] : null;
$description = new ApplicationDescription($application, $describedNamespace);
$this->write($application->getName()."\n".str_repeat('=', strlen($application->getName())));
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) {
return '* '.$commandName;
}, $namespace['commands'])));
}
foreach ($description->getCommands() as $command) {
$this->write("\n\n");
$this->write($this->describeCommand($command));
}
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Descriptor;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;
/**
* Text descriptor.
*
* @author Jean-François Simon <contact@jfsimon.fr>
*
* @internal
*/
class TextDescriptor extends Descriptor
{
/**
* {@inheritdoc}
*/
protected function describeInputArgument(InputArgument $argument, array $options = array())
{
if (null !== $argument->getDefault() && (!is_array($argument->getDefault()) || count($argument->getDefault()))) {
$default = sprintf('<comment> [default: %s]</comment>', $this->formatDefaultValue($argument->getDefault()));
} else {
$default = '';
}
$totalWidth = isset($options['total_width']) ? $options['total_width'] : strlen($argument->getName());
$spacingWidth = $totalWidth - strlen($argument->getName()) + 2;
$this->writeText(sprintf(' <info>%s</info>%s%s%s',
$argument->getName(),
str_repeat(' ', $spacingWidth),
// + 17 = 2 spaces + <info> + </info> + 2 spaces
preg_replace('/\s*[\r\n]\s*/', "\n".str_repeat(' ', $totalWidth + 17), $argument->getDescription()),
$default
), $options);
}
/**
* {@inheritdoc}
*/
protected function describeInputOption(InputOption $option, array $options = array())
{
if ($option->acceptValue() && null !== $option->getDefault() && (!is_array($option->getDefault()) || count($option->getDefault()))) {
$default = sprintf('<comment> [default: %s]</comment>', $this->formatDefaultValue($option->getDefault()));
} else {
$default = '';
}
$value = '';
if ($option->acceptValue()) {
$value = '='.strtoupper($option->getName());
if ($option->isValueOptional()) {
$value = '['.$value.']';
}
}
$totalWidth = isset($options['total_width']) ? $options['total_width'] : $this->calculateTotalWidthForOptions(array($option));
$synopsis = sprintf('%s%s',
$option->getShortcut() ? sprintf('-%s, ', $option->getShortcut()) : ' ',
sprintf('--%s%s', $option->getName(), $value)
);
$spacingWidth = $totalWidth - strlen($synopsis) + 2;
$this->writeText(sprintf(' <info>%s</info>%s%s%s%s',
$synopsis,
str_repeat(' ', $spacingWidth),
// + 17 = 2 spaces + <info> + </info> + 2 spaces
preg_replace('/\s*[\r\n]\s*/', "\n".str_repeat(' ', $totalWidth + 17), $option->getDescription()),
$default,
$option->isArray() ? '<comment> (multiple values allowed)</comment>' : ''
), $options);
}
/**
* {@inheritdoc}
*/
protected function describeInputDefinition(InputDefinition $definition, array $options = array())
{
$totalWidth = $this->calculateTotalWidthForOptions($definition->getOptions());
foreach ($definition->getArguments() as $argument) {
$totalWidth = max($totalWidth, strlen($argument->getName()));
}
if ($definition->getArguments()) {
$this->writeText('<comment>Arguments:</comment>', $options);
$this->writeText("\n");
foreach ($definition->getArguments() as $argument) {
$this->describeInputArgument($argument, array_merge($options, array('total_width' => $totalWidth)));
$this->writeText("\n");
}
}
if ($definition->getArguments() && $definition->getOptions()) {
$this->writeText("\n");
}
if ($definition->getOptions()) {
$laterOptions = array();
$this->writeText('<comment>Options:</comment>', $options);
foreach ($definition->getOptions() as $option) {
if (strlen($option->getShortcut()) > 1) {
$laterOptions[] = $option;
continue;
}
$this->writeText("\n");
$this->describeInputOption($option, array_merge($options, array('total_width' => $totalWidth)));
}
foreach ($laterOptions as $option) {
$this->writeText("\n");
$this->describeInputOption($option, array_merge($options, array('total_width' => $totalWidth)));
}
}
}
/**
* {@inheritdoc}
*/
protected function describeCommand(Command $command, array $options = array())
{
$command->getSynopsis(true);
$command->getSynopsis(false);
$command->mergeApplicationDefinition(false);
$this->writeText('<comment>Usage:</comment>', $options);
foreach (array_merge(array($command->getSynopsis(true)), $command->getAliases(), $command->getUsages()) as $usage) {
$this->writeText("\n");
$this->writeText(' '.$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('<comment>Help:</comment>', $options);
$this->writeText("\n");
$this->writeText(' '.str_replace("\n", "\n ", $help), $options);
$this->writeText("\n");
}
}
/**
* {@inheritdoc}
*/
protected function describeApplication(Application $application, array $options = array())
{
$describedNamespace = isset($options['namespace']) ? $options['namespace'] : null;
$description = new ApplicationDescription($application, $describedNamespace);
if (isset($options['raw_text']) && $options['raw_text']) {
$width = $this->getColumnWidth($description->getCommands());
foreach ($description->getCommands() as $command) {
$this->writeText(sprintf("%-{$width}s %s", $command->getName(), $command->getDescription()), $options);
$this->writeText("\n");
}
} else {
if ('' != $help = $application->getHelp()) {
$this->writeText("$help\n\n", $options);
}
$this->writeText("<comment>Usage:</comment>\n", $options);
$this->writeText(" command [options] [arguments]\n\n", $options);
$this->describeInputDefinition(new InputDefinition($application->getDefinition()->getOptions()), $options);
$this->writeText("\n");
$this->writeText("\n");
$width = $this->getColumnWidth($description->getCommands());
if ($describedNamespace) {
$this->writeText(sprintf('<comment>Available commands for the "%s" namespace:</comment>', $describedNamespace), $options);
} else {
$this->writeText('<comment>Available commands:</comment>', $options);
}
// add commands by namespace
foreach ($description->getNamespaces() as $namespace) {
if (!$describedNamespace && ApplicationDescription::GLOBAL_NAMESPACE !== $namespace['id']) {
$this->writeText("\n");
$this->writeText(' <comment>'.$namespace['id'].'</comment>', $options);
}
foreach ($namespace['commands'] as $name) {
$this->writeText("\n");
$spacingWidth = $width - strlen($name);
$this->writeText(sprintf(' <info>%s</info>%s%s', $name, str_repeat(' ', $spacingWidth), $description->getCommand($name)->getDescription()), $options);
}
}
$this->writeText("\n");
}
}
/**
* {@inheritdoc}
*/
private function writeText($content, array $options = array())
{
$this->write(
isset($options['raw_text']) && $options['raw_text'] ? strip_tags($content) : $content,
isset($options['raw_output']) ? !$options['raw_output'] : true
);
}
/**
* Formats input option/argument default value.
*
* @param mixed $default
*
* @return string
*/
private function formatDefaultValue($default)
{
return str_replace('\\\\', '\\', json_encode($default, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
}
/**
* @param Command[] $commands
*
* @return int
*/
private function getColumnWidth(array $commands)
{
$widths = array();
foreach ($commands as $command) {
$widths[] = strlen($command->getName());
foreach ($command->getAliases() as $alias) {
$widths[] = strlen($alias);
}
}
return max($widths) + 2;
}
/**
* @param InputOption[] $options
*
* @return int
*/
private function calculateTotalWidthForOptions($options)
{
$totalWidth = 0;
foreach ($options as $option) {
// "-" + shortcut + ", --" + name
$nameLength = 1 + max(strlen($option->getShortcut()), 1) + 4 + strlen($option->getName());
if ($option->acceptValue()) {
$valueLength = 1 + strlen($option->getName()); // = + value
$valueLength += $option->isValueOptional() ? 2 : 0; // [ + ]
$nameLength += $valueLength;
}
$totalWidth = max($totalWidth, $nameLength);
}
return $totalWidth;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Descriptor;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;
/**
* XML descriptor.
*
* @author Jean-François Simon <contact@jfsimon.fr>
*
* @internal
*/
class XmlDescriptor extends Descriptor
{
/**
* @param InputDefinition $definition
*
* @return \DOMDocument
*/
public function getInputDefinitionDocument(InputDefinition $definition)
{
$dom = new \DOMDocument('1.0', 'UTF-8');
$dom->appendChild($definitionXML = $dom->createElement('definition'));
$definitionXML->appendChild($argumentsXML = $dom->createElement('arguments'));
foreach ($definition->getArguments() as $argument) {
$this->appendDocument($argumentsXML, $this->getInputArgumentDocument($argument));
}
$definitionXML->appendChild($optionsXML = $dom->createElement('options'));
foreach ($definition->getOptions() as $option) {
$this->appendDocument($optionsXML, $this->getInputOptionDocument($option));
}
return $dom;
}
/**
* @param Command $command
*
* @return \DOMDocument
*/
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->appendChild($usagesXML = $dom->createElement('usages'));
foreach (array_merge(array($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;
}
/**
* @param Application $application
* @param string|null $namespace
*
* @return \DOMDocument
*/
public function getApplicationDocument(Application $application, $namespace = null)
{
$dom = new \DOMDocument('1.0', 'UTF-8');
$dom->appendChild($rootXml = $dom->createElement('symfony'));
if ($application->getName() !== 'UNKNOWN') {
$rootXml->setAttribute('name', $application->getName());
if ($application->getVersion() !== 'UNKNOWN') {
$rootXml->setAttribute('version', $application->getVersion());
}
}
$rootXml->appendChild($commandsXML = $dom->createElement('commands'));
$description = new ApplicationDescription($application, $namespace);
if ($namespace) {
$commandsXML->setAttribute('namespace', $namespace);
}
foreach ($description->getCommands() as $command) {
$this->appendDocument($commandsXML, $this->getCommandDocument($command));
}
if (!$namespace) {
$rootXml->appendChild($namespacesXML = $dom->createElement('namespaces'));
foreach ($description->getNamespaces() as $namespaceDescription) {
$namespacesXML->appendChild($namespaceArrayXML = $dom->createElement('namespace'));
$namespaceArrayXML->setAttribute('id', $namespaceDescription['id']);
foreach ($namespaceDescription['commands'] as $name) {
$namespaceArrayXML->appendChild($commandXML = $dom->createElement('command'));
$commandXML->appendChild($dom->createTextNode($name));
}
}
}
return $dom;
}
/**
* {@inheritdoc}
*/
protected function describeInputArgument(InputArgument $argument, array $options = array())
{
$this->writeDocument($this->getInputArgumentDocument($argument));
}
/**
* {@inheritdoc}
*/
protected function describeInputOption(InputOption $option, array $options = array())
{
$this->writeDocument($this->getInputOptionDocument($option));
}
/**
* {@inheritdoc}
*/
protected function describeInputDefinition(InputDefinition $definition, array $options = array())
{
$this->writeDocument($this->getInputDefinitionDocument($definition));
}
/**
* {@inheritdoc}
*/
protected function describeCommand(Command $command, array $options = array())
{
$this->writeDocument($this->getCommandDocument($command));
}
/**
* {@inheritdoc}
*/
protected function describeApplication(Application $application, array $options = array())
{
$this->writeDocument($this->getApplicationDocument($application, isset($options['namespace']) ? $options['namespace'] : null));
}
/**
* Appends document children to parent node.
*
* @param \DOMNode $parentNode
* @param \DOMNode $importedParent
*/
private function appendDocument(\DOMNode $parentNode, \DOMNode $importedParent)
{
foreach ($importedParent->childNodes as $childNode) {
$parentNode->appendChild($parentNode->ownerDocument->importNode($childNode, true));
}
}
/**
* Writes DOM document.
*
* @param \DOMDocument $dom
*
* @return \DOMDocument|string
*/
private function writeDocument(\DOMDocument $dom)
{
$dom->formatOutput = true;
$this->write($dom->saveXML());
}
/**
* @param InputArgument $argument
*
* @return \DOMDocument
*/
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()) ? array(var_export($argument->getDefault(), true)) : ($argument->getDefault() ? array($argument->getDefault()) : array()));
foreach ($defaults as $default) {
$defaultsXML->appendChild($defaultXML = $dom->createElement('default'));
$defaultXML->appendChild($dom->createTextNode($default));
}
return $dom;
}
/**
* @param InputOption $option
*
* @return \DOMDocument
*/
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', '-'.implode('|-', explode('|', $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()) ? array(var_export($option->getDefault(), true)) : ($option->getDefault() ? array($option->getDefault()) : array()));
$objectXML->appendChild($defaultsXML = $dom->createElement('defaults'));
if (!empty($defaults)) {
foreach ($defaults as $default) {
$defaultsXML->appendChild($defaultXML = $dom->createElement('default'));
$defaultXML->appendChild($dom->createTextNode($default));
}
}
}
return $dom;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Event;
/**
* Allows to do things before the command is executed, like skipping the command or changing the input.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class ConsoleCommandEvent extends ConsoleEvent
{
/**
* The return code for skipped commands, this will also be passed into the terminate event.
*/
const RETURN_CODE_DISABLED = 113;
/**
* Indicates if the command should be run or skipped.
*
* @var bool
*/
private $commandShouldRun = true;
/**
* Disables the command, so it won't be run.
*
* @return bool
*/
public function disableCommand()
{
return $this->commandShouldRun = false;
}
/**
* Enables the command.
*
* @return bool
*/
public function enableCommand()
{
return $this->commandShouldRun = true;
}
/**
* Returns true if the command is runnable, false otherwise.
*
* @return bool
*/
public function commandShouldRun()
{
return $this->commandShouldRun;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Event;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\EventDispatcher\Event;
/**
* Allows to inspect input and output of a command.
*
* @author Francesco Levorato <git@flevour.net>
*/
class ConsoleEvent extends Event
{
protected $command;
private $input;
private $output;
public function __construct(Command $command, InputInterface $input, OutputInterface $output)
{
$this->command = $command;
$this->input = $input;
$this->output = $output;
}
/**
* Gets the command that is executed.
*
* @return Command A Command instance
*/
public function getCommand()
{
return $this->command;
}
/**
* Gets the input instance.
*
* @return InputInterface An InputInterface instance
*/
public function getInput()
{
return $this->input;
}
/**
* Gets the output instance.
*
* @return OutputInterface An OutputInterface instance
*/
public function getOutput()
{
return $this->output;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Event;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Allows to handle exception thrown in a command.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class ConsoleExceptionEvent extends ConsoleEvent
{
private $exception;
private $exitCode;
public function __construct(Command $command, InputInterface $input, OutputInterface $output, \Exception $exception, $exitCode)
{
parent::__construct($command, $input, $output);
$this->setException($exception);
$this->exitCode = (int) $exitCode;
}
/**
* Returns the thrown exception.
*
* @return \Exception The thrown exception
*/
public function getException()
{
return $this->exception;
}
/**
* Replaces the thrown exception.
*
* This exception will be thrown if no response is set in the event.
*
* @param \Exception $exception The thrown exception
*/
public function setException(\Exception $exception)
{
$this->exception = $exception;
}
/**
* Gets the exit code.
*
* @return int The command exit code
*/
public function getExitCode()
{
return $this->exitCode;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Event;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Allows to manipulate the exit code of a command after its execution.
*
* @author Francesco Levorato <git@flevour.net>
*/
class ConsoleTerminateEvent extends ConsoleEvent
{
/**
* The exit code of the command.
*
* @var int
*/
private $exitCode;
public function __construct(Command $command, InputInterface $input, OutputInterface $output, $exitCode)
{
parent::__construct($command, $input, $output);
$this->setExitCode($exitCode);
}
/**
* Sets the exit code.
*
* @param int $exitCode The command exit code
*/
public function setExitCode($exitCode)
{
$this->exitCode = (int) $exitCode;
}
/**
* Gets the exit code.
*
* @return int The command exit code
*/
public function getExitCode()
{
return $this->exitCode;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Exception;
/**
* Represents an incorrect command name typed in the console.
*
* @author Jérôme Tamarelle <jerome@tamarelle.net>
*/
class CommandNotFoundException extends \InvalidArgumentException implements ExceptionInterface
{
private $alternatives;
/**
* @param string $message Exception message to throw
* @param array $alternatives List of similar defined names
* @param int $code Exception code
* @param Exception $previous previous exception used for the exception chaining
*/
public function __construct($message, array $alternatives = array(), $code = 0, \Exception $previous = null)
{
parent::__construct($message, $code, $previous);
$this->alternatives = $alternatives;
}
/**
* @return array A list of similar defined names
*/
public function getAlternatives()
{
return $this->alternatives;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Exception;
/**
* ExceptionInterface.
*
* @author Jérôme Tamarelle <jerome@tamarelle.net>
*/
interface ExceptionInterface
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Exception;
/**
* @author Jérôme Tamarelle <jerome@tamarelle.net>
*/
class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Exception;
/**
* Represents an incorrect option name typed in the console.
*
* @author Jérôme Tamarelle <jerome@tamarelle.net>
*/
class InvalidOptionException extends \InvalidArgumentException implements ExceptionInterface
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Exception;
/**
* @author Jérôme Tamarelle <jerome@tamarelle.net>
*/
class LogicException extends \LogicException implements ExceptionInterface
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Exception;
/**
* @author Jérôme Tamarelle <jerome@tamarelle.net>
*/
class RuntimeException extends \RuntimeException implements ExceptionInterface
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Formatter;
use Symfony\Component\Console\Exception\InvalidArgumentException;
/**
* Formatter class for console output.
*
* @author Konstantin Kudryashov <ever.zet@gmail.com>
*/
class OutputFormatter implements OutputFormatterInterface
{
private $decorated;
private $styles = array();
private $styleStack;
/**
* Escapes "<" special char in given text.
*
* @param string $text Text to escape
*
* @return string Escaped text
*/
public static function escape($text)
{
$text = preg_replace('/([^\\\\]?)</', '$1\\<', $text);
if ('\\' === substr($text, -1)) {
$len = strlen($text);
$text = rtrim($text, '\\');
$text .= str_repeat('<<', $len - strlen($text));
}
return $text;
}
/**
* Initializes console output formatter.
*
* @param bool $decorated Whether this formatter should actually decorate strings
* @param OutputFormatterStyleInterface[] $styles Array of "name => FormatterStyle" instances
*/
public function __construct($decorated = false, array $styles = array())
{
$this->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();
}
/**
* Sets the decorated flag.
*
* @param bool $decorated Whether to decorate the messages or not
*/
public function setDecorated($decorated)
{
$this->decorated = (bool) $decorated;
}
/**
* Gets the decorated flag.
*
* @return bool true if the output will decorate messages, false otherwise
*/
public function isDecorated()
{
return $this->decorated;
}
/**
* Sets a new style.
*
* @param string $name The style name
* @param OutputFormatterStyleInterface $style The style instance
*/
public function setStyle($name, OutputFormatterStyleInterface $style)
{
$this->styles[strtolower($name)] = $style;
}
/**
* Checks if output formatter has style with specified name.
*
* @param string $name
*
* @return bool
*/
public function hasStyle($name)
{
return isset($this->styles[strtolower($name)]);
}
/**
* Gets style options from style with specified name.
*
* @param string $name
*
* @return OutputFormatterStyleInterface
*
* @throws InvalidArgumentException When style isn't defined
*/
public function getStyle($name)
{
if (!$this->hasStyle($name)) {
throw new InvalidArgumentException(sprintf('Undefined style: %s', $name));
}
return $this->styles[strtolower($name)];
}
/**
* Formats a message according to the given styles.
*
* @param string $message The message to style
*
* @return string The styled message
*/
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;
}
// add the text up to the next tag
$output .= $this->applyCurrentStyle(substr($message, $offset, $pos - $offset));
$offset = $pos + strlen($text);
// opening tag?
if ($open = '/' != $text[1]) {
$tag = $matches[1][$i][0];
} else {
$tag = isset($matches[3][$i][0]) ? $matches[3][$i][0] : '';
}
if (!$open && !$tag) {
// </>
$this->styleStack->pop();
} elseif (false === $style = $this->createStyleFromString(strtolower($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, '<<')) {
return strtr($output, array('\\<' => '<', '<<' => '\\'));
}
return str_replace('\\<', '<', $output);
}
/**
* @return OutputFormatterStyleStack
*/
public function getStyleStack()
{
return $this->styleStack;
}
/**
* Tries to create new style instance from string.
*
* @param string $string
*
* @return OutputFormatterStyle|bool false if string is not format string
*/
private function createStyleFromString($string)
{
if (isset($this->styles[$string])) {
return $this->styles[$string];
}
if (!preg_match_all('/([^=]+)=([^;]+)(;|$)/', strtolower($string), $matches, PREG_SET_ORDER)) {
return false;
}
$style = new OutputFormatterStyle();
foreach ($matches as $match) {
array_shift($match);
if ('fg' == $match[0]) {
$style->setForeground($match[1]);
} elseif ('bg' == $match[0]) {
$style->setBackground($match[1]);
} else {
try {
$style->setOption($match[1]);
} catch (\InvalidArgumentException $e) {
return false;
}
}
}
return $style;
}
/**
* Applies current style from stack to text, if must be applied.
*
* @param string $text Input text
*
* @return string Styled text
*/
private function applyCurrentStyle($text)
{
return $this->isDecorated() && strlen($text) > 0 ? $this->styleStack->getCurrent()->apply($text) : $text;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Formatter;
/**
* Formatter interface for console output.
*
* @author Konstantin Kudryashov <ever.zet@gmail.com>
*/
interface OutputFormatterInterface
{
/**
* Sets the decorated flag.
*
* @param bool $decorated Whether to decorate the messages or not
*/
public function setDecorated($decorated);
/**
* Gets the decorated flag.
*
* @return bool true if the output will decorate messages, false otherwise
*/
public function isDecorated();
/**
* Sets a new style.
*
* @param string $name The style name
* @param OutputFormatterStyleInterface $style The style instance
*/
public function setStyle($name, OutputFormatterStyleInterface $style);
/**
* Checks if output formatter has style with specified name.
*
* @param string $name
*
* @return bool
*/
public function hasStyle($name);
/**
* Gets style options from style with specified name.
*
* @param string $name
*
* @return OutputFormatterStyleInterface
*/
public function getStyle($name);
/**
* Formats a message according to the given styles.
*
* @param string $message The message to style
*
* @return string The styled message
*/
public function format($message);
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Formatter;
use Symfony\Component\Console\Exception\InvalidArgumentException;
/**
* Formatter style class for defining styles.
*
* @author Konstantin Kudryashov <ever.zet@gmail.com>
*/
class OutputFormatterStyle implements OutputFormatterStyleInterface
{
private static $availableForegroundColors = array(
'black' => array('set' => 30, 'unset' => 39),
'red' => array('set' => 31, 'unset' => 39),
'green' => array('set' => 32, 'unset' => 39),
'yellow' => array('set' => 33, 'unset' => 39),
'blue' => array('set' => 34, 'unset' => 39),
'magenta' => array('set' => 35, 'unset' => 39),
'cyan' => array('set' => 36, 'unset' => 39),
'white' => array('set' => 37, 'unset' => 39),
'default' => array('set' => 39, 'unset' => 39),
);
private static $availableBackgroundColors = array(
'black' => array('set' => 40, 'unset' => 49),
'red' => array('set' => 41, 'unset' => 49),
'green' => array('set' => 42, 'unset' => 49),
'yellow' => array('set' => 43, 'unset' => 49),
'blue' => array('set' => 44, 'unset' => 49),
'magenta' => array('set' => 45, 'unset' => 49),
'cyan' => array('set' => 46, 'unset' => 49),
'white' => array('set' => 47, 'unset' => 49),
'default' => array('set' => 49, 'unset' => 49),
);
private static $availableOptions = array(
'bold' => array('set' => 1, 'unset' => 22),
'underscore' => array('set' => 4, 'unset' => 24),
'blink' => array('set' => 5, 'unset' => 25),
'reverse' => array('set' => 7, 'unset' => 27),
'conceal' => array('set' => 8, 'unset' => 28),
);
private $foreground;
private $background;
private $options = array();
/**
* Initializes output formatter style.
*
* @param string|null $foreground The style foreground color name
* @param string|null $background The style background color name
* @param array $options The style options
*/
public function __construct($foreground = null, $background = null, array $options = array())
{
if (null !== $foreground) {
$this->setForeground($foreground);
}
if (null !== $background) {
$this->setBackground($background);
}
if (count($options)) {
$this->setOptions($options);
}
}
/**
* Sets style foreground color.
*
* @param string|null $color The color name
*
* @throws InvalidArgumentException When the color name isn't defined
*/
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];
}
/**
* Sets style background color.
*
* @param string|null $color The color name
*
* @throws InvalidArgumentException When the color name isn't defined
*/
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];
}
/**
* Sets some specific style option.
*
* @param string $option The option name
*
* @throws InvalidArgumentException When the option name isn't defined
*/
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];
}
}
/**
* Unsets some specific style option.
*
* @param string $option The option name
*
* @throws InvalidArgumentException When the option name isn't defined
*/
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]);
}
}
/**
* Sets multiple style options at once.
*
* @param array $options
*/
public function setOptions(array $options)
{
$this->options = array();
foreach ($options as $option) {
$this->setOption($option);
}
}
/**
* Applies the style to a given text.
*
* @param string $text The text to style
*
* @return string
*/
public function apply($text)
{
$setCodes = array();
$unsetCodes = array();
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));
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Formatter;
/**
* Formatter style interface for defining styles.
*
* @author Konstantin Kudryashov <ever.zet@gmail.com>
*/
interface OutputFormatterStyleInterface
{
/**
* Sets style foreground color.
*
* @param string $color The color name
*/
public function setForeground($color = null);
/**
* Sets style background color.
*
* @param string $color The color name
*/
public function setBackground($color = null);
/**
* Sets some specific style option.
*
* @param string $option The option name
*/
public function setOption($option);
/**
* Unsets some specific style option.
*
* @param string $option The option name
*/
public function unsetOption($option);
/**
* Sets multiple style options at once.
*
* @param array $options
*/
public function setOptions(array $options);
/**
* Applies the style to a given text.
*
* @param string $text The text to style
*
* @return string
*/
public function apply($text);
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Formatter;
use Symfony\Component\Console\Exception\InvalidArgumentException;
/**
* @author Jean-François Simon <contact@jfsimon.fr>
*/
class OutputFormatterStyleStack
{
/**
* @var OutputFormatterStyleInterface[]
*/
private $styles;
/**
* @var OutputFormatterStyleInterface
*/
private $emptyStyle;
/**
* Constructor.
*
* @param OutputFormatterStyleInterface|null $emptyStyle
*/
public function __construct(OutputFormatterStyleInterface $emptyStyle = null)
{
$this->emptyStyle = $emptyStyle ?: new OutputFormatterStyle();
$this->reset();
}
/**
* Resets stack (ie. empty internal arrays).
*/
public function reset()
{
$this->styles = array();
}
/**
* Pushes a style in the stack.
*
* @param OutputFormatterStyleInterface $style
*/
public function push(OutputFormatterStyleInterface $style)
{
$this->styles[] = $style;
}
/**
* Pops a style from the stack.
*
* @param OutputFormatterStyleInterface|null $style
*
* @return OutputFormatterStyleInterface
*
* @throws InvalidArgumentException When style tags incorrectly nested
*/
public function pop(OutputFormatterStyleInterface $style = null)
{
if (empty($this->styles)) {
return $this->emptyStyle;
}
if (null === $style) {
return array_pop($this->styles);
}
foreach (array_reverse($this->styles, true) as $index => $stackedStyle) {
if ($style->apply('') === $stackedStyle->apply('')) {
$this->styles = array_slice($this->styles, 0, $index);
return $stackedStyle;
}
}
throw new InvalidArgumentException('Incorrectly nested style tag found.');
}
/**
* Computes current style with stacks top codes.
*
* @return OutputFormatterStyle
*/
public function getCurrent()
{
if (empty($this->styles)) {
return $this->emptyStyle;
}
return $this->styles[count($this->styles) - 1];
}
/**
* @param OutputFormatterStyleInterface $emptyStyle
*
* @return OutputFormatterStyleStack
*/
public function setEmptyStyle(OutputFormatterStyleInterface $emptyStyle)
{
$this->emptyStyle = $emptyStyle;
return $this;
}
/**
* @return OutputFormatterStyleInterface
*/
public function getEmptyStyle()
{
return $this->emptyStyle;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Helper;
/**
* Helps outputting debug information when running an external program from a command.
*
* An external program can be a Process, an HTTP request, or anything else.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class DebugFormatterHelper extends Helper
{
private $colors = array('black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white', 'default');
private $started = array();
private $count = -1;
/**
* Starts a debug formatting session.
*
* @param string $id The id of the formatting session
* @param string $message The message to display
* @param string $prefix The prefix to use
*
* @return string
*/
public function start($id, $message, $prefix = 'RUN')
{
$this->started[$id] = array('border' => ++$this->count % count($this->colors));
return sprintf("%s<bg=blue;fg=white> %s </> <fg=blue>%s</>\n", $this->getBorder($id), $prefix, $message);
}
/**
* Adds progress to a formatting session.
*
* @param string $id The id of the formatting session
* @param string $buffer The message to display
* @param bool $error Whether to consider the buffer as error
* @param string $prefix The prefix for output
* @param string $errorPrefix The prefix for error output
*
* @return string
*/
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<bg=red;fg=white> %s </> ', $this->getBorder($id), $errorPrefix);
$this->started[$id]['err'] = true;
}
$message .= str_replace("\n", sprintf("\n%s<bg=red;fg=white> %s </> ", $this->getBorder($id), $errorPrefix), $buffer);
} else {
if (isset($this->started[$id]['err'])) {
$message .= "\n";
unset($this->started[$id]['err']);
}
if (!isset($this->started[$id]['out'])) {
$message .= sprintf('%s<bg=green;fg=white> %s </> ', $this->getBorder($id), $prefix);
$this->started[$id]['out'] = true;
}
$message .= str_replace("\n", sprintf("\n%s<bg=green;fg=white> %s </> ", $this->getBorder($id), $prefix), $buffer);
}
return $message;
}
/**
* Stops a formatting session.
*
* @param string $id The id of the formatting session
* @param string $message The message to display
* @param bool $successful Whether to consider the result as success
* @param string $prefix The prefix for the end output
*
* @return string
*/
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<bg=green;fg=white> %s </> <fg=green>%s</>\n", $trailingEOL, $this->getBorder($id), $prefix, $message);
}
$message = sprintf("%s%s<bg=red;fg=white> %s </> <fg=red>%s</>\n", $trailingEOL, $this->getBorder($id), $prefix, $message);
unset($this->started[$id]['out'], $this->started[$id]['err']);
return $message;
}
/**
* @param string $id The id of the formatting session
*
* @return string
*/
private function getBorder($id)
{
return sprintf('<bg=%s> </>', $this->colors[$this->started[$id]['border']]);
}
/**
* {@inheritdoc}
*/
public function getName()
{
return 'debug_formatter';
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Helper;
use Symfony\Component\Console\Descriptor\DescriptorInterface;
use Symfony\Component\Console\Descriptor\JsonDescriptor;
use Symfony\Component\Console\Descriptor\MarkdownDescriptor;
use Symfony\Component\Console\Descriptor\TextDescriptor;
use Symfony\Component\Console\Descriptor\XmlDescriptor;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Exception\InvalidArgumentException;
/**
* This class adds helper method to describe objects in various formats.
*
* @author Jean-François Simon <contact@jfsimon.fr>
*/
class DescriptorHelper extends Helper
{
/**
* @var DescriptorInterface[]
*/
private $descriptors = array();
/**
* Constructor.
*/
public function __construct()
{
$this
->register('txt', new TextDescriptor())
->register('xml', new XmlDescriptor())
->register('json', new JsonDescriptor())
->register('md', new MarkdownDescriptor())
;
}
/**
* Describes an object if supported.
*
* Available options are:
* * format: string, the output format name
* * raw_text: boolean, sets output type as raw
*
* @param OutputInterface $output
* @param object $object
* @param array $options
*
* @throws InvalidArgumentException when the given format is not supported
*/
public function describe(OutputInterface $output, $object, array $options = array())
{
$options = array_merge(array(
'raw_text' => false,
'format' => 'txt',
), $options);
if (!isset($this->descriptors[$options['format']])) {
throw new InvalidArgumentException(sprintf('Unsupported format "%s".', $options['format']));
}
$descriptor = $this->descriptors[$options['format']];
$descriptor->describe($output, $object, $options);
}
/**
* Registers a descriptor.
*
* @param string $format
* @param DescriptorInterface $descriptor
*
* @return DescriptorHelper
*/
public function register($format, DescriptorInterface $descriptor)
{
$this->descriptors[$format] = $descriptor;
return $this;
}
/**
* {@inheritdoc}
*/
public function getName()
{
return 'descriptor';
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Helper;
use Symfony\Component\Console\Formatter\OutputFormatter;
/**
* The Formatter class provides helpers to format messages.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class FormatterHelper extends Helper
{
/**
* Formats a message within a section.
*
* @param string $section The section name
* @param string $message The message
* @param string $style The style to apply to the section
*
* @return string The format section
*/
public function formatSection($section, $message, $style = 'info')
{
return sprintf('<%s>[%s]</%s> %s', $style, $section, $style, $message);
}
/**
* Formats a message as a block of text.
*
* @param string|array $messages The message to write in the block
* @param string $style The style to apply to the whole block
* @param bool $large Whether to return a large block
*
* @return string The formatter message
*/
public function formatBlock($messages, $style, $large = false)
{
if (!is_array($messages)) {
$messages = array($messages);
}
$len = 0;
$lines = array();
foreach ($messages as $message) {
$message = OutputFormatter::escape($message);
$lines[] = sprintf($large ? ' %s ' : ' %s ', $message);
$len = max($this->strlen($message) + ($large ? 4 : 2), $len);
}
$messages = $large ? array(str_repeat(' ', $len)) : array();
for ($i = 0; isset($lines[$i]); ++$i) {
$messages[] = $lines[$i].str_repeat(' ', $len - $this->strlen($lines[$i]));
}
if ($large) {
$messages[] = str_repeat(' ', $len);
}
for ($i = 0; isset($messages[$i]); ++$i) {
$messages[$i] = sprintf('<%s>%s</%s>', $style, $messages[$i], $style);
}
return implode("\n", $messages);
}
/**
* Truncates a message to the given length.
*
* @param string $message
* @param int $length
* @param string $suffix
*
* @return string
*/
public function truncate($message, $length, $suffix = '...')
{
$computedLength = $length - $this->strlen($suffix);
if ($computedLength > $this->strlen($message)) {
return $message;
}
if (false === $encoding = mb_detect_encoding($message, null, true)) {
return substr($message, 0, $length).$suffix;
}
return mb_substr($message, 0, $length, $encoding).$suffix;
}
/**
* {@inheritdoc}
*/
public function getName()
{
return 'formatter';
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Helper;
use Symfony\Component\Console\Formatter\OutputFormatterInterface;
/**
* Helper is the base class for all helper classes.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
abstract class Helper implements HelperInterface
{
protected $helperSet = null;
/**
* Sets the helper set associated with this helper.
*
* @param HelperSet $helperSet A HelperSet instance
*/
public function setHelperSet(HelperSet $helperSet = null)
{
$this->helperSet = $helperSet;
}
/**
* Gets the helper set associated with this helper.
*
* @return HelperSet A HelperSet instance
*/
public function getHelperSet()
{
return $this->helperSet;
}
/**
* Returns the length of a string, using mb_strwidth if it is available.
*
* @param string $string The string to check its length
*
* @return int The length of the string
*/
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 formatTime($secs)
{
static $timeFormats = array(
array(0, '< 1 sec'),
array(1, '1 sec'),
array(2, 'secs', 1),
array(60, '1 min'),
array(120, 'mins', 60),
array(3600, '1 hr'),
array(7200, 'hrs', 3600),
array(86400, '1 day'),
array(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)
{
$isDecorated = $formatter->isDecorated();
$formatter->setDecorated(false);
// remove <...> formatting
$string = $formatter->format($string);
// remove already formatted characters
$string = preg_replace("/\033\[[^m]*m/", '', $string);
$formatter->setDecorated($isDecorated);
return self::strlen($string);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Helper;
/**
* HelperInterface is the interface all helpers must implement.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
interface HelperInterface
{
/**
* Sets the helper set associated with this helper.
*
* @param HelperSet $helperSet A HelperSet instance
*/
public function setHelperSet(HelperSet $helperSet = null);
/**
* Gets the helper set associated with this helper.
*
* @return HelperSet A HelperSet instance
*/
public function getHelperSet();
/**
* Returns the canonical name of this helper.
*
* @return string The canonical name
*/
public function getName();
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Helper;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\InvalidArgumentException;
/**
* HelperSet represents a set of helpers to be used with a command.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class HelperSet implements \IteratorAggregate
{
/**
* @var Helper[]
*/
private $helpers = array();
private $command;
/**
* Constructor.
*
* @param Helper[] $helpers An array of helper
*/
public function __construct(array $helpers = array())
{
foreach ($helpers as $alias => $helper) {
$this->set($helper, is_int($alias) ? null : $alias);
}
}
/**
* Sets a helper.
*
* @param HelperInterface $helper The helper instance
* @param string $alias An alias
*/
public function set(HelperInterface $helper, $alias = null)
{
$this->helpers[$helper->getName()] = $helper;
if (null !== $alias) {
$this->helpers[$alias] = $helper;
}
$helper->setHelperSet($this);
}
/**
* Returns true if the helper if defined.
*
* @param string $name The helper name
*
* @return bool true if the helper is defined, false otherwise
*/
public function has($name)
{
return isset($this->helpers[$name]);
}
/**
* Gets a helper value.
*
* @param string $name The helper name
*
* @return HelperInterface The helper instance
*
* @throws InvalidArgumentException if the helper is not defined
*/
public function get($name)
{
if (!$this->has($name)) {
throw new InvalidArgumentException(sprintf('The helper "%s" is not defined.', $name));
}
return $this->helpers[$name];
}
/**
* Sets the command associated with this helper set.
*
* @param Command $command A Command instance
*/
public function setCommand(Command $command = null)
{
$this->command = $command;
}
/**
* Gets the command associated with this helper set.
*
* @return Command A Command instance
*/
public function getCommand()
{
return $this->command;
}
/**
* @return Helper[]
*/
public function getIterator()
{
return new \ArrayIterator($this->helpers);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Helper;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputAwareInterface;
/**
* An implementation of InputAwareInterface for Helpers.
*
* @author Wouter J <waldio.webdesign@gmail.com>
*/
abstract class InputAwareHelper extends Helper implements InputAwareInterface
{
protected $input;
/**
* {@inheritdoc}
*/
public function setInput(InputInterface $input)
{
$this->input = $input;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Helper;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Process\Exception\ProcessFailedException;
use Symfony\Component\Process\Process;
use Symfony\Component\Process\ProcessBuilder;
/**
* The ProcessHelper class provides helpers to run external processes.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class ProcessHelper extends Helper
{
/**
* Runs an external process.
*
* @param OutputInterface $output An OutputInterface instance
* @param string|array|Process $cmd An instance of Process or an array of arguments to escape and run or a command to run
* @param string|null $error An error message that must be displayed if something went wrong
* @param callable|null $callback A PHP callback to run whenever there is some
* output available on STDOUT or STDERR
* @param int $verbosity The threshold for verbosity
*
* @return Process The process that ran
*/
public function run(OutputInterface $output, $cmd, $error = null, callable $callback = null, $verbosity = OutputInterface::VERBOSITY_VERY_VERBOSE)
{
if ($output instanceof ConsoleOutputInterface) {
$output = $output->getErrorOutput();
}
$formatter = $this->getHelperSet()->get('debug_formatter');
if (is_array($cmd)) {
$process = ProcessBuilder::create($cmd)->getProcess();
} elseif ($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('<error>%s</error>', $this->escapeString($error)));
}
return $process;
}
/**
* Runs the process.
*
* This is identical to run() except that an exception is thrown if the process
* exits with a non-zero exit code.
*
* @param OutputInterface $output An OutputInterface instance
* @param string|Process $cmd An instance of Process or a command to run
* @param string|null $error An error message that must be displayed if something went wrong
* @param callable|null $callback A PHP callback to run whenever there is some
* output available on STDOUT or STDERR
*
* @return Process The process that ran
*
* @throws ProcessFailedException
*
* @see run()
*/
public function mustRun(OutputInterface $output, $cmd, $error = null, callable $callback = null)
{
$process = $this->run($output, $cmd, $error, $callback);
if (!$process->isSuccessful()) {
throw new ProcessFailedException($process);
}
return $process;
}
/**
* Wraps a Process callback to add debugging output.
*
* @param OutputInterface $output An OutputInterface interface
* @param Process $process The Process
* @param callable|null $callback A PHP callable
*
* @return callable
*/
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);
}
/**
* {@inheritdoc}
*/
public function getName()
{
return 'process';
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Helper;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Exception\LogicException;
/**
* The ProgressBar provides helpers to display progress output.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Chris Jones <leeked@gmail.com>
*/
class ProgressBar
{
// options
private $barWidth = 28;
private $barChar;
private $emptyBarChar = '-';
private $progressChar = '>';
private $format;
private $internalFormat;
private $redrawFreq = 1;
/**
* @var OutputInterface
*/
private $output;
private $step = 0;
private $max;
private $startTime;
private $stepWidth;
private $percent = 0.0;
private $formatLineCount;
private $messages = array();
private $overwrite = true;
private $firstRun = true;
private static $formatters;
private static $formats;
/**
* Constructor.
*
* @param OutputInterface $output An OutputInterface instance
* @param int $max Maximum steps (0 if unknown)
*/
public function __construct(OutputInterface $output, $max = 0)
{
if ($output instanceof ConsoleOutputInterface) {
$output = $output->getErrorOutput();
}
$this->output = $output;
$this->setMaxSteps($max);
if (!$this->output->isDecorated()) {
// disable overwrite when output does not support ANSI codes.
$this->overwrite = false;
// set a reasonable redraw frequency so output isn't flooded
$this->setRedrawFrequency($max / 10);
}
$this->startTime = time();
}
/**
* Sets a placeholder formatter for a given name.
*
* This method also allow you to override an existing placeholder.
*
* @param string $name The placeholder name (including the delimiter char like %)
* @param callable $callable A PHP callable
*/
public static function setPlaceholderFormatterDefinition($name, callable $callable)
{
if (!self::$formatters) {
self::$formatters = self::initPlaceholderFormatters();
}
self::$formatters[$name] = $callable;
}
/**
* Gets the placeholder formatter for a given name.
*
* @param string $name The placeholder name (including the delimiter char like %)
*
* @return callable|null A PHP callable
*/
public static function getPlaceholderFormatterDefinition($name)
{
if (!self::$formatters) {
self::$formatters = self::initPlaceholderFormatters();
}
return isset(self::$formatters[$name]) ? self::$formatters[$name] : null;
}
/**
* Sets a format for a given name.
*
* This method also allow you to override an existing format.
*
* @param string $name The format name
* @param string $format A format string
*/
public static function setFormatDefinition($name, $format)
{
if (!self::$formats) {
self::$formats = self::initFormats();
}
self::$formats[$name] = $format;
}
/**
* Gets the format for a given name.
*
* @param string $name The format name
*
* @return string|null A format string
*/
public static function getFormatDefinition($name)
{
if (!self::$formats) {
self::$formats = self::initFormats();
}
return isset(self::$formats[$name]) ? self::$formats[$name] : null;
}
/**
* Associates a text with a named placeholder.
*
* The text is displayed when the progress bar is rendered but only
* when the corresponding placeholder is part of the custom format line
* (by wrapping the name with %).
*
* @param string $message The text to associate with the placeholder
* @param string $name The name of the placeholder
*/
public function setMessage($message, $name = 'message')
{
$this->messages[$name] = $message;
}
public function getMessage($name = 'message')
{
return $this->messages[$name];
}
/**
* Gets the progress bar start time.
*
* @return int The progress bar start time
*/
public function getStartTime()
{
return $this->startTime;
}
/**
* Gets the progress bar maximal steps.
*
* @return int The progress bar max steps
*/
public function getMaxSteps()
{
return $this->max;
}
/**
* Gets the current step position.
*
* @return int The progress bar step
*/
public function getProgress()
{
return $this->step;
}
/**
* Gets the progress bar step width.
*
* @return int The progress bar step width
*/
private function getStepWidth()
{
return $this->stepWidth;
}
/**
* Gets the current progress bar percent.
*
* @return float The current progress bar percent
*/
public function getProgressPercent()
{
return $this->percent;
}
/**
* Sets the progress bar width.
*
* @param int $size The progress bar size
*/
public function setBarWidth($size)
{
$this->barWidth = (int) $size;
}
/**
* Gets the progress bar width.
*
* @return int The progress bar size
*/
public function getBarWidth()
{
return $this->barWidth;
}
/**
* Sets the bar character.
*
* @param string $char A character
*/
public function setBarCharacter($char)
{
$this->barChar = $char;
}
/**
* Gets the bar character.
*
* @return string A character
*/
public function getBarCharacter()
{
if (null === $this->barChar) {
return $this->max ? '=' : $this->emptyBarChar;
}
return $this->barChar;
}
/**
* Sets the empty bar character.
*
* @param string $char A character
*/
public function setEmptyBarCharacter($char)
{
$this->emptyBarChar = $char;
}
/**
* Gets the empty bar character.
*
* @return string A character
*/
public function getEmptyBarCharacter()
{
return $this->emptyBarChar;
}
/**
* Sets the progress bar character.
*
* @param string $char A character
*/
public function setProgressCharacter($char)
{
$this->progressChar = $char;
}
/**
* Gets the progress bar character.
*
* @return string A character
*/
public function getProgressCharacter()
{
return $this->progressChar;
}
/**
* Sets the progress bar format.
*
* @param string $format The format
*/
public function setFormat($format)
{
$this->format = null;
$this->internalFormat = $format;
}
/**
* Sets the redraw frequency.
*
* @param int|float $freq The frequency in steps
*/
public function setRedrawFrequency($freq)
{
$this->redrawFreq = max((int) $freq, 1);
}
/**
* Starts the progress output.
*
* @param int|null $max Number of steps to complete the bar (0 if indeterminate), null to leave unchanged
*/
public function start($max = null)
{
$this->startTime = time();
$this->step = 0;
$this->percent = 0.0;
if (null !== $max) {
$this->setMaxSteps($max);
}
$this->display();
}
/**
* Advances the progress output X steps.
*
* @param int $step Number of steps to advance
*
* @throws LogicException
*/
public function advance($step = 1)
{
$this->setProgress($this->step + $step);
}
/**
* Sets whether to overwrite the progressbar, false for new line.
*
* @param bool $overwrite
*/
public function setOverwrite($overwrite)
{
$this->overwrite = (bool) $overwrite;
}
/**
* Sets the current progress.
*
* @param int $step The current progress
*
* @throws LogicException
*/
public function setProgress($step)
{
$step = (int) $step;
if ($step < $this->step) {
throw new LogicException('You can\'t regress the progress bar.');
}
if ($this->max && $step > $this->max) {
$this->max = $step;
}
$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();
}
}
/**
* Finishes the progress output.
*/
public function finish()
{
if (!$this->max) {
$this->max = $this->step;
}
if ($this->step === $this->max && !$this->overwrite) {
// prevent double 100% output
return;
}
$this->setProgress($this->max);
}
/**
* Outputs the current progress string.
*/
public function display()
{
if (OutputInterface::VERBOSITY_QUIET === $this->output->getVerbosity()) {
return;
}
if (null === $this->format) {
$this->setRealFormat($this->internalFormat ?: $this->determineBestFormat());
}
$this->overwrite(preg_replace_callback("{%([a-z\-_]+)(?:\:([^%]+))?%}i", 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;
}, $this->format));
}
/**
* Removes the progress bar from the current line.
*
* This is useful if you wish to write some output
* while a progress bar is running.
* Call display() to show the progress bar again.
*/
public function clear()
{
if (!$this->overwrite) {
return;
}
if (null === $this->format) {
$this->setRealFormat($this->internalFormat ?: $this->determineBestFormat());
}
$this->overwrite('');
}
/**
* Sets the progress bar format.
*
* @param string $format The format
*/
private function setRealFormat($format)
{
// try to use the _nomax variant if available
if (!$this->max && null !== self::getFormatDefinition($format.'_nomax')) {
$this->format = self::getFormatDefinition($format.'_nomax');
} elseif (null !== self::getFormatDefinition($format)) {
$this->format = self::getFormatDefinition($format);
} else {
$this->format = $format;
}
$this->formatLineCount = substr_count($this->format, "\n");
}
/**
* Sets the progress bar maximal steps.
*
* @param int $max The progress bar max steps
*/
private function setMaxSteps($max)
{
$this->max = max(0, (int) $max);
$this->stepWidth = $this->max ? Helper::strlen($this->max) : 4;
}
/**
* Overwrites a previous message to the output.
*
* @param string $message The message
*/
private function overwrite($message)
{
if ($this->overwrite) {
if (!$this->firstRun) {
// Move the cursor to the beginning of the line
$this->output->write("\x0D");
// Erase the line
$this->output->write("\x1B[2K");
// Erase previous lines
if ($this->formatLineCount > 0) {
$this->output->write(str_repeat("\x1B[1A\x1B[2K", $this->formatLineCount));
}
}
} elseif ($this->step > 0) {
$this->output->writeln('');
}
$this->firstRun = false;
$this->output->write($message);
}
private function determineBestFormat()
{
switch ($this->output->getVerbosity()) {
// OutputInterface::VERBOSITY_QUIET: display is disabled anyway
case OutputInterface::VERBOSITY_VERBOSE:
return $this->max ? 'verbose' : 'verbose_nomax';
case OutputInterface::VERBOSITY_VERY_VERBOSE:
return $this->max ? 'very_verbose' : 'very_verbose_nomax';
case OutputInterface::VERBOSITY_DEBUG:
return $this->max ? 'debug' : 'debug_nomax';
default:
return $this->max ? 'normal' : 'normal_nomax';
}
}
private static function initPlaceholderFormatters()
{
return array(
'bar' => function (ProgressBar $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 (ProgressBar $bar) {
return Helper::formatTime(time() - $bar->getStartTime());
},
'remaining' => function (ProgressBar $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 (ProgressBar $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 (ProgressBar $bar) {
return Helper::formatMemory(memory_get_usage(true));
},
'current' => function (ProgressBar $bar) {
return str_pad($bar->getProgress(), $bar->getStepWidth(), ' ', STR_PAD_LEFT);
},
'max' => function (ProgressBar $bar) {
return $bar->getMaxSteps();
},
'percent' => function (ProgressBar $bar) {
return floor($bar->getProgressPercent() * 100);
},
);
}
private static function initFormats()
{
return array(
'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%',
);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Helper;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\Console\Output\OutputInterface;
/**
* @author Kevin Bond <kevinbond@gmail.com>
*/
class ProgressIndicator
{
private $output;
private $startTime;
private $format;
private $message;
private $indicatorValues;
private $indicatorCurrent;
private $indicatorChangeInterval;
private $indicatorUpdateTime;
private $lastMessagesLength;
private $started = false;
private static $formatters;
private static $formats;
/**
* @param OutputInterface $output
* @param string|null $format Indicator format
* @param int $indicatorChangeInterval Change interval in milliseconds
* @param array|null $indicatorValues Animated indicator characters
*/
public function __construct(OutputInterface $output, $format = null, $indicatorChangeInterval = 100, $indicatorValues = null)
{
$this->output = $output;
if (null === $format) {
$format = $this->determineBestFormat();
}
if (null === $indicatorValues) {
$indicatorValues = array('-', '\\', '|', '/');
}
$indicatorValues = array_values($indicatorValues);
if (2 > count($indicatorValues)) {
throw new InvalidArgumentException('Must have at least 2 indicator value characters.');
}
$this->format = self::getFormatDefinition($format);
$this->indicatorChangeInterval = $indicatorChangeInterval;
$this->indicatorValues = $indicatorValues;
$this->startTime = time();
}
/**
* Sets the current indicator message.
*
* @param string|null $message
*/
public function setMessage($message)
{
$this->message = $message;
$this->display();
}
/**
* Starts the indicator output.
*
* @param $message
*/
public function start($message)
{
if ($this->started) {
throw new LogicException('Progress indicator already started.');
}
$this->message = $message;
$this->started = true;
$this->lastMessagesLength = 0;
$this->startTime = time();
$this->indicatorUpdateTime = $this->getCurrentTimeInMilliseconds() + $this->indicatorChangeInterval;
$this->indicatorCurrent = 0;
$this->display();
}
/**
* Advances the indicator.
*/
public function advance()
{
if (!$this->started) {
throw new LogicException('Progress indicator has not yet been started.');
}
if (!$this->output->isDecorated()) {
return;
}
$currentTime = $this->getCurrentTimeInMilliseconds();
if ($currentTime < $this->indicatorUpdateTime) {
return;
}
$this->indicatorUpdateTime = $currentTime + $this->indicatorChangeInterval;
++$this->indicatorCurrent;
$this->display();
}
/**
* Finish the indicator with message.
*
* @param $message
*/
public function finish($message)
{
if (!$this->started) {
throw new LogicException('Progress indicator has not yet been started.');
}
$this->message = $message;
$this->display();
$this->output->writeln('');
$this->started = false;
}
/**
* Gets the format for a given name.
*
* @param string $name The format name
*
* @return string|null A format string
*/
public static function getFormatDefinition($name)
{
if (!self::$formats) {
self::$formats = self::initFormats();
}
return isset(self::$formats[$name]) ? self::$formats[$name] : null;
}
/**
* Sets a placeholder formatter for a given name.
*
* This method also allow you to override an existing placeholder.
*
* @param string $name The placeholder name (including the delimiter char like %)
* @param callable $callable A PHP callable
*/
public static function setPlaceholderFormatterDefinition($name, $callable)
{
if (!self::$formatters) {
self::$formatters = self::initPlaceholderFormatters();
}
self::$formatters[$name] = $callable;
}
/**
* Gets the placeholder formatter for a given name.
*
* @param string $name The placeholder name (including the delimiter char like %)
*
* @return callable|null A PHP callable
*/
public static function getPlaceholderFormatterDefinition($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()) {
// OutputInterface::VERBOSITY_QUIET: display is disabled anyway
case OutputInterface::VERBOSITY_VERBOSE:
return $this->output->isDecorated() ? 'verbose' : 'verbose_no_ansi';
case OutputInterface::VERBOSITY_VERY_VERBOSE:
case OutputInterface::VERBOSITY_DEBUG:
return $this->output->isDecorated() ? 'very_verbose' : 'very_verbose_no_ansi';
default:
return $this->output->isDecorated() ? 'normal' : 'normal_no_ansi';
}
}
/**
* Overwrites a previous message to the output.
*
* @param string $message The message
*/
private function overwrite($message)
{
// append whitespace to match the line's length
if (null !== $this->lastMessagesLength) {
if ($this->lastMessagesLength > Helper::strlenWithoutDecoration($this->output->getFormatter(), $message)) {
$message = str_pad($message, $this->lastMessagesLength, "\x20", STR_PAD_RIGHT);
}
}
if ($this->output->isDecorated()) {
$this->output->write("\x0D");
$this->output->write($message);
} else {
$this->output->writeln($message);
}
$this->lastMessagesLength = 0;
$len = Helper::strlenWithoutDecoration($this->output->getFormatter(), $message);
if ($len > $this->lastMessagesLength) {
$this->lastMessagesLength = $len;
}
}
private function getCurrentTimeInMilliseconds()
{
return round(microtime(true) * 1000);
}
private static function initPlaceholderFormatters()
{
return array(
'indicator' => function (ProgressIndicator $indicator) {
return $indicator->indicatorValues[$indicator->indicatorCurrent % count($indicator->indicatorValues)];
},
'message' => function (ProgressIndicator $indicator) {
return $indicator->message;
},
'elapsed' => function (ProgressIndicator $indicator) {
return Helper::formatTime(time() - $indicator->startTime);
},
'memory' => function () {
return Helper::formatMemory(memory_get_usage(true));
},
);
}
private static function initFormats()
{
return array(
'normal' => ' %indicator% %message%',
'normal_no_ansi' => ' %message%',
'verbose' => ' %indicator% %message% (%elapsed:6s%)',
'verbose_no_ansi' => ' %message% (%elapsed:6s%)',
'very_verbose' => ' %indicator% %message% (%elapsed:6s%, %memory:6s%)',
'very_verbose_no_ansi' => ' %message% (%elapsed:6s%, %memory:6s%)',
);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Helper;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
use Symfony\Component\Console\Question\Question;
use Symfony\Component\Console\Question\ChoiceQuestion;
/**
* The QuestionHelper class provides helpers to interact with the user.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class QuestionHelper extends Helper
{
private $inputStream;
private static $shell;
private static $stty;
/**
* Asks a question to the user.
*
* @param InputInterface $input An InputInterface instance
* @param OutputInterface $output An OutputInterface instance
* @param Question $question The question to ask
*
* @return string The user answer
*
* @throws RuntimeException If there is no data to read in the input stream
*/
public function ask(InputInterface $input, OutputInterface $output, Question $question)
{
if ($output instanceof ConsoleOutputInterface) {
$output = $output->getErrorOutput();
}
if (!$input->isInteractive()) {
return $question->getDefault();
}
if (!$question->getValidator()) {
return $this->doAsk($output, $question);
}
$interviewer = function () use ($output, $question) {
return $this->doAsk($output, $question);
};
return $this->validateAttempts($interviewer, $output, $question);
}
/**
* Sets the input stream to read from when interacting with the user.
*
* This is mainly useful for testing purpose.
*
* @param resource $stream The input stream
*
* @throws InvalidArgumentException In case the stream is not a resource
*/
public function setInputStream($stream)
{
if (!is_resource($stream)) {
throw new InvalidArgumentException('Input stream must be a valid resource.');
}
$this->inputStream = $stream;
}
/**
* Returns the helper's input stream.
*
* @return resource
*/
public function getInputStream()
{
return $this->inputStream;
}
/**
* {@inheritdoc}
*/
public function getName()
{
return 'question';
}
/**
* Asks the question to the user.
*
* @param OutputInterface $output
* @param Question $question
*
* @return bool|mixed|null|string
*
* @throws \Exception
* @throws \RuntimeException
*/
private function doAsk(OutputInterface $output, Question $question)
{
$this->writePrompt($output, $question);
$inputStream = $this->inputStream ?: STDIN;
$autocomplete = $question->getAutocompleterValues();
if (null === $autocomplete || !$this->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));
}
$ret = strlen($ret) > 0 ? $ret : $question->getDefault();
if ($normalizer = $question->getNormalizer()) {
return $normalizer($ret);
}
return $ret;
}
/**
* Outputs the question prompt.
*
* @param OutputInterface $output
* @param Question $question
*/
protected function writePrompt(OutputInterface $output, Question $question)
{
$message = $question->getQuestion();
if ($question instanceof ChoiceQuestion) {
$maxWidth = max(array_map(array($this, 'strlen'), array_keys($question->getChoices())));
$messages = (array) $question->getQuestion();
foreach ($question->getChoices() as $key => $value) {
$width = $maxWidth - $this->strlen($key);
$messages[] = ' [<info>'.$key.str_repeat(' ', $width).'</info>] '.$value;
}
$output->writeln($messages);
$message = $question->getPrompt();
}
$output->write($message);
}
/**
* Outputs an error message.
*
* @param OutputInterface $output
* @param \Exception $error
*/
protected function writeError(OutputInterface $output, \Exception $error)
{
if (null !== $this->getHelperSet() && $this->getHelperSet()->has('formatter')) {
$message = $this->getHelperSet()->get('formatter')->formatBlock($error->getMessage(), 'error');
} else {
$message = '<error>'.$error->getMessage().'</error>';
}
$output->writeln($message);
}
/**
* Autocompletes a question.
*
* @param OutputInterface $output
* @param Question $question
* @param resource $inputStream
*
* @return string
*/
private function autocomplete(OutputInterface $output, Question $question, $inputStream)
{
$autocomplete = $question->getAutocompleterValues();
$ret = '';
$i = 0;
$ofs = -1;
$matches = $autocomplete;
$numMatches = count($matches);
$sttyMode = shell_exec('stty -g');
// Disable icanon (so we can fread each keypress) and echo (we'll do echoing here instead)
shell_exec('stty -icanon -echo');
// Add highlighted text style
$output->getFormatter()->setStyle('hl', new OutputFormatterStyle('black', 'white'));
// Read a keypress
while (!feof($inputStream)) {
$c = fread($inputStream, 1);
// Backspace Character
if ("\177" === $c) {
if (0 === $numMatches && 0 !== $i) {
--$i;
// Move cursor backwards
$output->write("\033[1D");
}
if ($i === 0) {
$ofs = -1;
$matches = $autocomplete;
$numMatches = count($matches);
} else {
$numMatches = 0;
}
// Pop the last character off the end of our string
$ret = substr($ret, 0, $i);
} elseif ("\033" === $c) {
// Did we read an escape sequence?
$c .= fread($inputStream, 2);
// A = Up Arrow. B = Down Arrow
if (isset($c[2]) && ('A' === $c[2] || 'B' === $c[2])) {
if ('A' === $c[2] && -1 === $ofs) {
$ofs = 0;
}
if (0 === $numMatches) {
continue;
}
$ofs += ('A' === $c[2]) ? -1 : 1;
$ofs = ($numMatches + $ofs) % $numMatches;
}
} elseif (ord($c) < 32) {
if ("\t" === $c || "\n" === $c) {
if ($numMatches > 0 && -1 !== $ofs) {
$ret = $matches[$ofs];
// Echo out remaining chars for current match
$output->write(substr($ret, $i));
$i = strlen($ret);
}
if ("\n" === $c) {
$output->write($c);
break;
}
$numMatches = 0;
}
continue;
} else {
$output->write($c);
$ret .= $c;
++$i;
$numMatches = 0;
$ofs = 0;
foreach ($autocomplete as $value) {
// If typed characters match the beginning chunk of value (e.g. [AcmeDe]moBundle)
if (0 === strpos($value, $ret) && $i !== strlen($value)) {
$matches[$numMatches++] = $value;
}
}
}
// Erase characters from cursor to end of line
$output->write("\033[K");
if ($numMatches > 0 && -1 !== $ofs) {
// Save cursor position
$output->write("\0337");
// Write highlighted text
$output->write('<hl>'.substr($matches[$ofs], $i).'</hl>');
// Restore cursor position
$output->write("\0338");
}
}
// Reset stty so it behaves normally again
shell_exec(sprintf('stty %s', $sttyMode));
return $ret;
}
/**
* Gets a hidden response from user.
*
* @param OutputInterface $output An Output instance
* @param resource $inputStream The handler resource
*
* @return string The answer
*
* @throws RuntimeException In case the fallback is deactivated and the response cannot be hidden
*/
private function getHiddenResponse(OutputInterface $output, $inputStream)
{
if ('\\' === DIRECTORY_SEPARATOR) {
$exe = __DIR__.'/../Resources/bin/hiddeninput.exe';
// handle code running from a phar
if ('phar:' === substr(__FILE__, 0, 5)) {
$tmpExe = sys_get_temp_dir().'/hiddeninput.exe';
copy($exe, $tmpExe);
$exe = $tmpExe;
}
$value = rtrim(shell_exec($exe));
$output->writeln('');
if (isset($tmpExe)) {
unlink($tmpExe);
}
return $value;
}
if ($this->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 = $shell === 'csh' ? '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.');
}
/**
* Validates an attempt.
*
* @param callable $interviewer A callable that will ask for a question and return the result
* @param OutputInterface $output An Output instance
* @param Question $question A Question instance
*
* @return string The validated response
*
* @throws \Exception In case the max number of attempts has been reached and no valid response has been given
*/
private function validateAttempts(callable $interviewer, OutputInterface $output, Question $question)
{
$error = null;
$attempts = $question->getMaxAttempts();
while (null === $attempts || $attempts--) {
if (null !== $error) {
$this->writeError($output, $error);
}
try {
return call_user_func($question->getValidator(), $interviewer());
} catch (\Exception $error) {
}
}
throw $error;
}
/**
* Returns a valid unix shell.
*
* @return string|bool The valid shell name, false in case no valid shell is found
*/
private function getShell()
{
if (null !== self::$shell) {
return self::$shell;
}
self::$shell = false;
if (file_exists('/usr/bin/env')) {
// handle other OSs with bash/zsh/ksh/csh if available to hide the answer
$test = "/usr/bin/env %s -c 'echo OK' 2> /dev/null";
foreach (array('bash', 'zsh', 'ksh', 'csh') as $sh) {
if ('OK' === rtrim(shell_exec(sprintf($test, $sh)))) {
self::$shell = $sh;
break;
}
}
}
return self::$shell;
}
/**
* Returns whether Stty is available or not.
*
* @return bool
*/
private function hasSttyAvailable()
{
if (null !== self::$stty) {
return self::$stty;
}
exec('stty 2>&1', $output, $exitcode);
return self::$stty = $exitcode === 0;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Helper;
use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ChoiceQuestion;
use Symfony\Component\Console\Question\ConfirmationQuestion;
use Symfony\Component\Console\Question\Question;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\Console\Formatter\OutputFormatter;
/**
* Symfony Style Guide compliant question helper.
*
* @author Kevin Bond <kevinbond@gmail.com>
*/
class SymfonyQuestionHelper extends QuestionHelper
{
/**
* {@inheritdoc}
*/
public function ask(InputInterface $input, OutputInterface $output, Question $question)
{
$validator = $question->getValidator();
$question->setValidator(function ($value) use ($validator) {
if (null !== $validator) {
$value = $validator($value);
} else {
// make required
if (!is_array($value) && !is_bool($value) && 0 === strlen($value)) {
throw new LogicException('A value is required.');
}
}
return $value;
});
return parent::ask($input, $output, $question);
}
/**
* {@inheritdoc}
*/
protected function writePrompt(OutputInterface $output, Question $question)
{
$text = OutputFormatter::escape($question->getQuestion());
$default = $question->getDefault();
switch (true) {
case null === $default:
$text = sprintf(' <info>%s</info>:', $text);
break;
case $question instanceof ConfirmationQuestion:
$text = sprintf(' <info>%s (yes/no)</info> [<comment>%s</comment>]:', $text, $default ? 'yes' : 'no');
break;
case $question instanceof ChoiceQuestion && $question->isMultiselect():
$choices = $question->getChoices();
$default = explode(',', $default);
foreach ($default as $key => $value) {
$default[$key] = $choices[trim($value)];
}
$text = sprintf(' <info>%s</info> [<comment>%s</comment>]:', $text, OutputFormatter::escape(implode(', ', $default)));
break;
case $question instanceof ChoiceQuestion:
$choices = $question->getChoices();
$text = sprintf(' <info>%s</info> [<comment>%s</comment>]:', $text, OutputFormatter::escape($choices[$default]));
break;
default:
$text = sprintf(' <info>%s</info> [<comment>%s</comment>]:', $text, OutputFormatter::escape($default));
}
$output->writeln($text);
if ($question instanceof ChoiceQuestion) {
$width = max(array_map('strlen', array_keys($question->getChoices())));
foreach ($question->getChoices() as $key => $value) {
$output->writeln(sprintf(" [<comment>%-${width}s</comment>] %s", $key, $value));
}
}
$output->write(' > ');
}
/**
* {@inheritdoc}
*/
protected function writeError(OutputInterface $output, \Exception $error)
{
if ($output instanceof SymfonyStyle) {
$output->newLine();
$output->error($error->getMessage());
return;
}
parent::writeError($output, $error);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Helper;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Exception\InvalidArgumentException;
/**
* Provides helpers to display a table.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Саша Стаменковић <umpirsky@gmail.com>
* @author Abdellatif Ait boudad <a.aitboudad@gmail.com>
* @author Max Grigorian <maxakawizard@gmail.com>
*/
class Table
{
/**
* Table headers.
*
* @var array
*/
private $headers = array();
/**
* Table rows.
*
* @var array
*/
private $rows = array();
/**
* Column widths cache.
*
* @var array
*/
private $effectiveColumnWidths = array();
/**
* Number of columns cache.
*
* @var array
*/
private $numberOfColumns;
/**
* @var OutputInterface
*/
private $output;
/**
* @var TableStyle
*/
private $style;
/**
* @var array
*/
private $columnStyles = array();
/**
* User set column widths.
*
* @var array
*/
private $columnWidths = array();
private static $styles;
public function __construct(OutputInterface $output)
{
$this->output = $output;
if (!self::$styles) {
self::$styles = self::initStyles();
}
$this->setStyle('default');
}
/**
* Sets a style definition.
*
* @param string $name The style name
* @param TableStyle $style A TableStyle instance
*/
public static function setStyleDefinition($name, TableStyle $style)
{
if (!self::$styles) {
self::$styles = self::initStyles();
}
self::$styles[$name] = $style;
}
/**
* Gets a style definition by name.
*
* @param string $name The style name
*
* @return TableStyle A TableStyle instance
*/
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));
}
/**
* Sets table style.
*
* @param TableStyle|string $name The style name or a TableStyle instance
*
* @return Table
*/
public function setStyle($name)
{
$this->style = $this->resolveStyle($name);
return $this;
}
/**
* Gets the current table style.
*
* @return TableStyle
*/
public function getStyle()
{
return $this->style;
}
/**
* Sets table column style.
*
* @param int $columnIndex Column index
* @param TableStyle|string $name The style name or a TableStyle instance
*
* @return Table
*/
public function setColumnStyle($columnIndex, $name)
{
$columnIndex = intval($columnIndex);
$this->columnStyles[$columnIndex] = $this->resolveStyle($name);
return $this;
}
/**
* Gets the current style for a column.
*
* If style was not set, it returns the global table style.
*
* @param int $columnIndex Column index
*
* @return TableStyle
*/
public function getColumnStyle($columnIndex)
{
if (isset($this->columnStyles[$columnIndex])) {
return $this->columnStyles[$columnIndex];
}
return $this->getStyle();
}
/**
* Sets the minimum width of a column.
*
* @param int $columnIndex Column index
* @param int $width Minimum column width in characters
*
* @return Table
*/
public function setColumnWidth($columnIndex, $width)
{
$this->columnWidths[intval($columnIndex)] = intval($width);
return $this;
}
/**
* Sets the minimum width of all columns.
*
* @param array $widths
*
* @return Table
*/
public function setColumnWidths(array $widths)
{
$this->columnWidths = array();
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 = array($headers);
}
$this->headers = $headers;
return $this;
}
public function setRows(array $rows)
{
$this->rows = array();
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;
}
/**
* Renders table to output.
*
* Example:
* +---------------+-----------------------+------------------+
* | ISBN | Title | Author |
* +---------------+-----------------------+------------------+
* | 99921-58-10-7 | Divine Comedy | Dante Alighieri |
* | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens |
* | 960-425-059-0 | The Lord of the Rings | J. R. R. Tolkien |
* +---------------+-----------------------+------------------+
*/
public function render()
{
$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();
}
/**
* Renders horizontal header separator.
*
* Example: +-----+-----------+-------+
*/
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));
}
/**
* Renders vertical column separator.
*/
private function renderColumnSeparator()
{
return sprintf($this->style->getBorderFormat(), $this->style->getVerticalBorderChar());
}
/**
* Renders table row.
*
* Example: | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens |
*
* @param array $row
* @param string $cellFormat
*/
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);
}
/**
* Renders table cell with padding.
*
* @param array $row
* @param int $column
* @param string $cellFormat
*/
private function renderCell(array $row, $column, $cellFormat)
{
$cell = isset($row[$column]) ? $row[$column] : '';
$width = $this->effectiveColumnWidths[$column];
if ($cell instanceof TableCell && $cell->getColspan() > 1) {
// add the width of the following columns(numbers of colspan).
foreach (range($column + 1, $column + $cell->getColspan() - 1) as $nextColumn) {
$width += $this->getColumnSeparatorWidth() + $this->effectiveColumnWidths[$nextColumn];
}
}
// str_pad won't work properly with multi-byte strings, we need to fix the padding
if (false !== $encoding = mb_detect_encoding($cell, null, true)) {
$width += strlen($cell) - mb_strwidth($cell, $encoding);
}
$style = $this->getColumnStyle($column);
if ($cell instanceof TableSeparator) {
return sprintf($style->getBorderFormat(), str_repeat($style->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()));
}
/**
* Calculate number of columns for this table.
*/
private function calculateNumberOfColumns()
{
if (null !== $this->numberOfColumns) {
return;
}
$columns = array(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 = array();
for ($rowKey = 0; $rowKey < count($rows); ++$rowKey) {
$rows = $this->fillNextRows($rows, $rowKey);
// Remove any new line breaks and replace it with a new line
foreach ($rows[$rowKey] as $column => $cell) {
if (!strstr($cell, "\n")) {
continue;
}
$lines = explode("\n", $cell);
foreach ($lines as $lineKey => $line) {
if ($cell instanceof TableCell) {
$line = new TableCell($line, array('colspan' => $cell->getColspan()));
}
if (0 === $lineKey) {
$rows[$rowKey][$column] = $line;
} else {
$unmergedRows[$rowKey][$lineKey][$column] = $line;
}
}
}
}
$tableRows = array();
foreach ($rows as $rowKey => $row) {
$tableRows[] = $this->fillCells($row);
if (isset($unmergedRows[$rowKey])) {
$tableRows = array_merge($tableRows, $unmergedRows[$rowKey]);
}
}
return $tableRows;
}
/**
* fill rows that contains rowspan > 1.
*
* @param array $rows
* @param int $line
*
* @return array
*/
private function fillNextRows($rows, $line)
{
$unmergedRows = array();
foreach ($rows[$line] as $column => $cell) {
if ($cell instanceof TableCell && $cell->getRowspan() > 1) {
$nbLines = $cell->getRowspan() - 1;
$lines = array($cell);
if (strstr($cell, "\n")) {
$lines = explode("\n", $cell);
$nbLines = count($lines) > $nbLines ? substr_count($cell, "\n") : $nbLines;
$rows[$line][$column] = new TableCell($lines[0], array('colspan' => $cell->getColspan()));
unset($lines[0]);
}
// create a two dimensional array (rowspan x colspan)
$unmergedRows = array_replace_recursive(array_fill($line + 1, $nbLines, array()), $unmergedRows);
foreach ($unmergedRows as $unmergedRowKey => $unmergedRow) {
$value = isset($lines[$unmergedRowKey - $line]) ? $lines[$unmergedRowKey - $line] : '';
$unmergedRows[$unmergedRowKey][$column] = new TableCell($value, array('colspan' => $cell->getColspan()));
}
}
}
foreach ($unmergedRows as $unmergedRowKey => $unmergedRow) {
// we need to know if $unmergedRow will be merged or inserted into $rows
if (isset($rows[$unmergedRowKey]) && is_array($rows[$unmergedRowKey]) && ($this->getNumberOfColumns($rows[$unmergedRowKey]) + $this->getNumberOfColumns($unmergedRows[$unmergedRowKey]) <= $this->numberOfColumns)) {
foreach ($unmergedRow as $cellKey => $cell) {
// insert cell into row at cellKey position
array_splice($rows[$unmergedRowKey], $cellKey, 0, array($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, array($row));
}
}
return $rows;
}
/**
* fill cells for a row that contains colspan > 1.
*
* @param array $row
*
* @return array
*/
private function fillCells($row)
{
$newRow = array();
foreach ($row as $column => $cell) {
$newRow[] = $cell;
if ($cell instanceof TableCell && $cell->getColspan() > 1) {
foreach (range($column + 1, $column + $cell->getColspan() - 1) as $position) {
// insert empty value at column position
$newRow[] = '';
}
}
}
return $newRow ?: $row;
}
/**
* @param array $rows
* @param int $line
*
* @return array
*/
private function copyRow($rows, $line)
{
$row = $rows[$line];
foreach ($row as $cellKey => $cellValue) {
$row[$cellKey] = '';
if ($cellValue instanceof TableCell) {
$row[$cellKey] = new TableCell('', array('colspan' => $cellValue->getColspan()));
}
}
return $row;
}
/**
* Gets number of columns by row.
*
* @param array $row
*
* @return int
*/
private function getNumberOfColumns(array $row)
{
$columns = count($row);
foreach ($row as $column) {
$columns += $column instanceof TableCell ? ($column->getColspan() - 1) : 0;
}
return $columns;
}
/**
* Gets list of columns for the given row.
*
* @param array $row
*
* @return array
*/
private function getRowColumns($row)
{
$columns = range(0, $this->numberOfColumns - 1);
foreach ($row as $cellKey => $cell) {
if ($cell instanceof TableCell && $cell->getColspan() > 1) {
// exclude grouped columns.
$columns = array_diff($columns, range($cellKey + 1, $cellKey + $cell->getColspan() - 1));
}
}
return $columns;
}
/**
* Calculates columns widths.
*
* @param array $rows
*/
private function calculateColumnsWidth($rows)
{
for ($column = 0; $column < $this->numberOfColumns; ++$column) {
$lengths = array();
foreach ($rows as $row) {
if ($row instanceof TableSeparator) {
continue;
}
foreach ($row as $i => $cell) {
if ($cell instanceof TableCell) {
$textLength = strlen($cell);
if ($textLength > 0) {
$contentColumns = str_split($cell, ceil($textLength / $cell->getColspan()));
foreach ($contentColumns as $position => $content) {
$row[$i + $position] = $content;
}
}
}
}
$lengths[] = $this->getCellWidth($row, $column);
}
$this->effectiveColumnWidths[$column] = max($lengths) + strlen($this->style->getCellRowContentFormat()) - 2;
}
}
/**
* Gets column width.
*
* @return int
*/
private function getColumnSeparatorWidth()
{
return strlen(sprintf($this->style->getBorderFormat(), $this->style->getVerticalBorderChar()));
}
/**
* Gets cell width.
*
* @param array $row
* @param int $column
*
* @return int
*/
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);
}
/**
* Called after rendering to cleanup cache data.
*/
private function cleanup()
{
$this->effectiveColumnWidths = array();
$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 array(
'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));
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Helper;
use Symfony\Component\Console\Exception\InvalidArgumentException;
/**
* @author Abdellatif Ait boudad <a.aitboudad@gmail.com>
*/
class TableCell
{
/**
* @var string
*/
private $value;
/**
* @var array
*/
private $options = array(
'rowspan' => 1,
'colspan' => 1,
);
/**
* @param string $value
* @param array $options
*/
public function __construct($value = '', array $options = array())
{
$this->value = $value;
// check option names
if ($diff = array_diff(array_keys($options), array_keys($this->options))) {
throw new InvalidArgumentException(sprintf('The TableCell does not support the following options: \'%s\'.', implode('\', \'', $diff)));
}
$this->options = array_merge($this->options, $options);
}
/**
* Returns the cell value.
*
* @return string
*/
public function __toString()
{
return $this->value;
}
/**
* Gets number of colspan.
*
* @return int
*/
public function getColspan()
{
return (int) $this->options['colspan'];
}
/**
* Gets number of rowspan.
*
* @return int
*/
public function getRowspan()
{
return (int) $this->options['rowspan'];
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Helper;
/**
* Marks a row as being a separator.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class TableSeparator extends TableCell
{
/**
* @param array $options
*/
public function __construct(array $options = array())
{
parent::__construct('', $options);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Helper;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\LogicException;
/**
* Defines the styles for a Table.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Саша Стаменковић <umpirsky@gmail.com>
*/
class TableStyle
{
private $paddingChar = ' ';
private $horizontalBorderChar = '-';
private $verticalBorderChar = '|';
private $crossingChar = '+';
private $cellHeaderFormat = '<info>%s</info>';
private $cellRowFormat = '%s';
private $cellRowContentFormat = ' %s ';
private $borderFormat = '%s';
private $padType = STR_PAD_RIGHT;
/**
* Sets padding character, used for cell padding.
*
* @param string $paddingChar
*
* @return TableStyle
*/
public function setPaddingChar($paddingChar)
{
if (!$paddingChar) {
throw new LogicException('The padding char must not be empty');
}
$this->paddingChar = $paddingChar;
return $this;
}
/**
* Gets padding character, used for cell padding.
*
* @return string
*/
public function getPaddingChar()
{
return $this->paddingChar;
}
/**
* Sets horizontal border character.
*
* @param string $horizontalBorderChar
*
* @return TableStyle
*/
public function setHorizontalBorderChar($horizontalBorderChar)
{
$this->horizontalBorderChar = $horizontalBorderChar;
return $this;
}
/**
* Gets horizontal border character.
*
* @return string
*/
public function getHorizontalBorderChar()
{
return $this->horizontalBorderChar;
}
/**
* Sets vertical border character.
*
* @param string $verticalBorderChar
*
* @return TableStyle
*/
public function setVerticalBorderChar($verticalBorderChar)
{
$this->verticalBorderChar = $verticalBorderChar;
return $this;
}
/**
* Gets vertical border character.
*
* @return string
*/
public function getVerticalBorderChar()
{
return $this->verticalBorderChar;
}
/**
* Sets crossing character.
*
* @param string $crossingChar
*
* @return TableStyle
*/
public function setCrossingChar($crossingChar)
{
$this->crossingChar = $crossingChar;
return $this;
}
/**
* Gets crossing character.
*
* @return string $crossingChar
*/
public function getCrossingChar()
{
return $this->crossingChar;
}
/**
* Sets header cell format.
*
* @param string $cellHeaderFormat
*
* @return TableStyle
*/
public function setCellHeaderFormat($cellHeaderFormat)
{
$this->cellHeaderFormat = $cellHeaderFormat;
return $this;
}
/**
* Gets header cell format.
*
* @return string
*/
public function getCellHeaderFormat()
{
return $this->cellHeaderFormat;
}
/**
* Sets row cell format.
*
* @param string $cellRowFormat
*
* @return TableStyle
*/
public function setCellRowFormat($cellRowFormat)
{
$this->cellRowFormat = $cellRowFormat;
return $this;
}
/**
* Gets row cell format.
*
* @return string
*/
public function getCellRowFormat()
{
return $this->cellRowFormat;
}
/**
* Sets row cell content format.
*
* @param string $cellRowContentFormat
*
* @return TableStyle
*/
public function setCellRowContentFormat($cellRowContentFormat)
{
$this->cellRowContentFormat = $cellRowContentFormat;
return $this;
}
/**
* Gets row cell content format.
*
* @return string
*/
public function getCellRowContentFormat()
{
return $this->cellRowContentFormat;
}
/**
* Sets table border format.
*
* @param string $borderFormat
*
* @return TableStyle
*/
public function setBorderFormat($borderFormat)
{
$this->borderFormat = $borderFormat;
return $this;
}
/**
* Gets table border format.
*
* @return string
*/
public function getBorderFormat()
{
return $this->borderFormat;
}
/**
* Sets cell padding type.
*
* @param int $padType STR_PAD_*
*
* @return TableStyle
*/
public function setPadType($padType)
{
if (!in_array($padType, array(STR_PAD_LEFT, STR_PAD_RIGHT, STR_PAD_BOTH), true)) {
throw new InvalidArgumentException('Invalid padding type. Expected one of (STR_PAD_LEFT, STR_PAD_RIGHT, STR_PAD_BOTH).');
}
$this->padType = $padType;
return $this;
}
/**
* Gets cell padding type.
*
* @return int
*/
public function getPadType()
{
return $this->padType;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Input;
use Symfony\Component\Console\Exception\RuntimeException;
/**
* ArgvInput represents an input coming from the CLI arguments.
*
* Usage:
*
* $input = new ArgvInput();
*
* By default, the `$_SERVER['argv']` array is used for the input values.
*
* This can be overridden by explicitly passing the input values in the constructor:
*
* $input = new ArgvInput($_SERVER['argv']);
*
* If you pass it yourself, don't forget that the first element of the array
* is the name of the running application.
*
* When passing an argument to the constructor, be sure that it respects
* the same rules as the argv one. It's almost always better to use the
* `StringInput` when you want to provide your own input.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @see http://www.gnu.org/software/libc/manual/html_node/Argument-Syntax.html
* @see http://www.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap12.html#tag_12_02
*/
class ArgvInput extends Input
{
private $tokens;
private $parsed;
/**
* Constructor.
*
* @param array|null $argv An array of parameters from the CLI (in the argv format)
* @param InputDefinition|null $definition A InputDefinition instance
*/
public function __construct(array $argv = null, InputDefinition $definition = null)
{
if (null === $argv) {
$argv = $_SERVER['argv'];
}
// strip the application name
array_shift($argv);
$this->tokens = $argv;
parent::__construct($definition);
}
protected function setTokens(array $tokens)
{
$this->tokens = $tokens;
}
/**
* {@inheritdoc}
*/
protected function parse()
{
$parseOptions = true;
$this->parsed = $this->tokens;
while (null !== $token = array_shift($this->parsed)) {
if ($parseOptions && '' == $token) {
$this->parseArgument($token);
} elseif ($parseOptions && '--' == $token) {
$parseOptions = false;
} elseif ($parseOptions && 0 === strpos($token, '--')) {
$this->parseLongOption($token);
} elseif ($parseOptions && '-' === $token[0] && '-' !== $token) {
$this->parseShortOption($token);
} else {
$this->parseArgument($token);
}
}
}
/**
* Parses a short option.
*
* @param string $token The current 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()) {
// an option with a value (with no space)
$this->addShortOption($name[0], substr($name, 1));
} else {
$this->parseShortOptionSet($name);
}
} else {
$this->addShortOption($name, null);
}
}
/**
* Parses a short option set.
*
* @param string $name The current token
*
* @throws RuntimeException When option given doesn't exist
*/
private function parseShortOptionSet($name)
{
$len = strlen($name);
for ($i = 0; $i < $len; ++$i) {
if (!$this->definition->hasShortcut($name[$i])) {
throw new RuntimeException(sprintf('The "-%s" option does not exist.', $name[$i]));
}
$option = $this->definition->getOptionForShortcut($name[$i]);
if ($option->acceptValue()) {
$this->addLongOption($option->getName(), $i === $len - 1 ? null : substr($name, $i + 1));
break;
} else {
$this->addLongOption($option->getName(), null);
}
}
}
/**
* Parses a long option.
*
* @param string $token The current token
*/
private function parseLongOption($token)
{
$name = substr($token, 2);
if (false !== $pos = strpos($name, '=')) {
if (0 === strlen($value = substr($name, $pos + 1))) {
array_unshift($this->parsed, null);
}
$this->addLongOption(substr($name, 0, $pos), $value);
} else {
$this->addLongOption($name, null);
}
}
/**
* Parses an argument.
*
* @param string $token The current token
*
* @throws RuntimeException When too many arguments are given
*/
private function parseArgument($token)
{
$c = count($this->arguments);
// if input is expecting another argument, add it
if ($this->definition->hasArgument($c)) {
$arg = $this->definition->getArgument($c);
$this->arguments[$arg->getName()] = $arg->isArray() ? array($token) : $token;
// if last argument isArray(), append token to last argument
} elseif ($this->definition->hasArgument($c - 1) && $this->definition->getArgument($c - 1)->isArray()) {
$arg = $this->definition->getArgument($c - 1);
$this->arguments[$arg->getName()][] = $token;
// unexpected argument
} else {
$all = $this->definition->getArguments();
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));
}
}
/**
* Adds a short option value.
*
* @param string $shortcut The short option key
* @param mixed $value The value for the option
*
* @throws RuntimeException When option given doesn't exist
*/
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);
}
/**
* Adds a long option value.
*
* @param string $name The long option key
* @param mixed $value The value for the option
*
* @throws RuntimeException When option given doesn't exist
*/
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);
// Convert empty values to null
if (!isset($value[0])) {
$value = null;
}
if (null !== $value && !$option->acceptValue()) {
throw new RuntimeException(sprintf('The "--%s" option does not accept a value.', $name));
}
if (null === $value && $option->acceptValue() && count($this->parsed)) {
// if option accepts an optional or mandatory argument
// let's see if there is one provided
$next = array_shift($this->parsed);
if (isset($next[0]) && '-' !== $next[0]) {
$value = $next;
} elseif (empty($next)) {
$value = null;
} 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()) {
$value = $option->isValueOptional() ? $option->getDefault() : true;
}
}
if ($option->isArray()) {
$this->options[$name][] = $value;
} else {
$this->options[$name] = $value;
}
}
/**
* {@inheritdoc}
*/
public function getFirstArgument()
{
foreach ($this->tokens as $token) {
if ($token && '-' === $token[0]) {
continue;
}
return $token;
}
}
/**
* {@inheritdoc}
*/
public function hasParameterOption($values, $onlyParams = false)
{
$values = (array) $values;
foreach ($this->tokens as $token) {
if ($onlyParams && $token === '--') {
return false;
}
foreach ($values as $value) {
if ($token === $value || 0 === strpos($token, $value.'=')) {
return true;
}
}
}
return false;
}
/**
* {@inheritdoc}
*/
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 false;
}
foreach ($values as $value) {
if ($token === $value || 0 === strpos($token, $value.'=')) {
if (false !== $pos = strpos($token, '=')) {
return substr($token, $pos + 1);
}
return array_shift($tokens);
}
}
}
return $default;
}
/**
* Returns a stringified representation of the args passed to the command.
*
* @return string
*/
public function __toString()
{
$tokens = array_map(function ($token) {
if (preg_match('{^(-[^=]+=)(.+)}', $token, $match)) {
return $match[1].$this->escapeToken($match[2]);
}
if ($token && $token[0] !== '-') {
return $this->escapeToken($token);
}
return $token;
}, $this->tokens);
return implode(' ', $tokens);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Input;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\InvalidOptionException;
/**
* ArrayInput represents an input provided as an array.
*
* Usage:
*
* $input = new ArrayInput(array('name' => 'foo', '--bar' => 'foobar'));
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class ArrayInput extends Input
{
private $parameters;
/**
* Constructor.
*
* @param array $parameters An array of parameters
* @param InputDefinition|null $definition A InputDefinition instance
*/
public function __construct(array $parameters, InputDefinition $definition = null)
{
$this->parameters = $parameters;
parent::__construct($definition);
}
/**
* {@inheritdoc}
*/
public function getFirstArgument()
{
foreach ($this->parameters as $key => $value) {
if ($key && '-' === $key[0]) {
continue;
}
return $value;
}
}
/**
* {@inheritdoc}
*/
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;
}
/**
* {@inheritdoc}
*/
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 false;
}
if (is_int($k)) {
if (in_array($v, $values)) {
return true;
}
} elseif (in_array($k, $values)) {
return $v;
}
}
return $default;
}
/**
* Returns a stringified representation of the args passed to the command.
*
* @return string
*/
public function __toString()
{
$params = array();
foreach ($this->parameters as $param => $val) {
if ($param && '-' === $param[0]) {
$params[] = $param.('' != $val ? '='.$this->escapeToken($val) : '');
} else {
$params[] = $this->escapeToken($val);
}
}
return implode(' ', $params);
}
/**
* {@inheritdoc}
*/
protected function parse()
{
foreach ($this->parameters as $key => $value) {
if ($key === '--') {
return;
}
if (0 === strpos($key, '--')) {
$this->addLongOption(substr($key, 2), $value);
} elseif ('-' === $key[0]) {
$this->addShortOption(substr($key, 1), $value);
} else {
$this->addArgument($key, $value);
}
}
}
/**
* Adds a short option value.
*
* @param string $shortcut The short option key
* @param mixed $value The value for the option
*
* @throws InvalidOptionException When option given doesn't exist
*/
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);
}
/**
* Adds a long option value.
*
* @param string $name The long option key
* @param mixed $value The value for the option
*
* @throws InvalidOptionException When option given doesn't exist
* @throws InvalidOptionException When a required value is missing
*/
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));
}
$value = $option->isValueOptional() ? $option->getDefault() : true;
}
$this->options[$name] = $value;
}
/**
* Adds an argument value.
*
* @param string $name The argument name
* @param mixed $value The value for the argument
*
* @throws InvalidArgumentException When argument given doesn't exist
*/
private function addArgument($name, $value)
{
if (!$this->definition->hasArgument($name)) {
throw new InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name));
}
$this->arguments[$name] = $value;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Input;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\RuntimeException;
/**
* Input is the base class for all concrete Input classes.
*
* Three concrete classes are provided by default:
*
* * `ArgvInput`: The input comes from the CLI arguments (argv)
* * `StringInput`: The input is provided as a string
* * `ArrayInput`: The input is provided as an array
*
* @author Fabien Potencier <fabien@symfony.com>
*/
abstract class Input implements InputInterface
{
/**
* @var InputDefinition
*/
protected $definition;
protected $options = array();
protected $arguments = array();
protected $interactive = true;
/**
* Constructor.
*
* @param InputDefinition|null $definition A InputDefinition instance
*/
public function __construct(InputDefinition $definition = null)
{
if (null === $definition) {
$this->definition = new InputDefinition();
} else {
$this->bind($definition);
$this->validate();
}
}
/**
* {@inheritdoc}
*/
public function bind(InputDefinition $definition)
{
$this->arguments = array();
$this->options = array();
$this->definition = $definition;
$this->parse();
}
/**
* Processes command line arguments.
*/
abstract protected function parse();
/**
* {@inheritdoc}
*/
public function validate()
{
$definition = $this->definition;
$givenArguments = $this->arguments;
$missingArguments = array_filter(array_keys($definition->getArguments()), function ($argument) use ($definition, $givenArguments) {
return !array_key_exists($argument, $givenArguments) && $definition->getArgument($argument)->isRequired();
});
if (count($missingArguments) > 0) {
throw new RuntimeException(sprintf('Not enough arguments (missing: "%s").', implode(', ', $missingArguments)));
}
}
/**
* {@inheritdoc}
*/
public function isInteractive()
{
return $this->interactive;
}
/**
* {@inheritdoc}
*/
public function setInteractive($interactive)
{
$this->interactive = (bool) $interactive;
}
/**
* {@inheritdoc}
*/
public function getArguments()
{
return array_merge($this->definition->getArgumentDefaults(), $this->arguments);
}
/**
* {@inheritdoc}
*/
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();
}
/**
* {@inheritdoc}
*/
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;
}
/**
* {@inheritdoc}
*/
public function hasArgument($name)
{
return $this->definition->hasArgument($name);
}
/**
* {@inheritdoc}
*/
public function getOptions()
{
return array_merge($this->definition->getOptionDefaults(), $this->options);
}
/**
* {@inheritdoc}
*/
public function getOption($name)
{
if (!$this->definition->hasOption($name)) {
throw new InvalidArgumentException(sprintf('The "%s" option does not exist.', $name));
}
return isset($this->options[$name]) ? $this->options[$name] : $this->definition->getOption($name)->getDefault();
}
/**
* {@inheritdoc}
*/
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;
}
/**
* {@inheritdoc}
*/
public function hasOption($name)
{
return $this->definition->hasOption($name);
}
/**
* Escapes a token through escapeshellarg if it contains unsafe chars.
*
* @param string $token
*
* @return string
*/
public function escapeToken($token)
{
return preg_match('{^[\w-]+$}', $token) ? $token : escapeshellarg($token);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Input;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\LogicException;
/**
* Represents a command line argument.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class InputArgument
{
const REQUIRED = 1;
const OPTIONAL = 2;
const IS_ARRAY = 4;
private $name;
private $mode;
private $default;
private $description;
/**
* Constructor.
*
* @param string $name The argument name
* @param int $mode The argument mode: self::REQUIRED or self::OPTIONAL
* @param string $description A description text
* @param mixed $default The default value (for self::OPTIONAL mode only)
*
* @throws InvalidArgumentException When argument mode is not valid
*/
public function __construct($name, $mode = null, $description = '', $default = null)
{
if (null === $mode) {
$mode = self::OPTIONAL;
} elseif (!is_int($mode) || $mode > 7 || $mode < 1) {
throw new InvalidArgumentException(sprintf('Argument mode "%s" is not valid.', $mode));
}
$this->name = $name;
$this->mode = $mode;
$this->description = $description;
$this->setDefault($default);
}
/**
* Returns the argument name.
*
* @return string The argument name
*/
public function getName()
{
return $this->name;
}
/**
* Returns true if the argument is required.
*
* @return bool true if parameter mode is self::REQUIRED, false otherwise
*/
public function isRequired()
{
return self::REQUIRED === (self::REQUIRED & $this->mode);
}
/**
* Returns true if the argument can take multiple values.
*
* @return bool true if mode is self::IS_ARRAY, false otherwise
*/
public function isArray()
{
return self::IS_ARRAY === (self::IS_ARRAY & $this->mode);
}
/**
* Sets the default value.
*
* @param mixed $default The default value
*
* @throws LogicException When incorrect default value is given
*/
public function setDefault($default = null)
{
if (self::REQUIRED === $this->mode && null !== $default) {
throw new LogicException('Cannot set a default value except for InputArgument::OPTIONAL mode.');
}
if ($this->isArray()) {
if (null === $default) {
$default = array();
} elseif (!is_array($default)) {
throw new LogicException('A default value for an array argument must be an array.');
}
}
$this->default = $default;
}
/**
* Returns the default value.
*
* @return mixed The default value
*/
public function getDefault()
{
return $this->default;
}
/**
* Returns the description text.
*
* @return string The description text
*/
public function getDescription()
{
return $this->description;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Input;
/**
* InputAwareInterface should be implemented by classes that depends on the
* Console Input.
*
* @author Wouter J <waldio.webdesign@gmail.com>
*/
interface InputAwareInterface
{
/**
* Sets the Console Input.
*
* @param InputInterface
*/
public function setInput(InputInterface $input);
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Input;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\LogicException;
/**
* A InputDefinition represents a set of valid command line arguments and options.
*
* Usage:
*
* $definition = new InputDefinition(array(
* new InputArgument('name', InputArgument::REQUIRED),
* new InputOption('foo', 'f', InputOption::VALUE_REQUIRED),
* ));
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class InputDefinition
{
private $arguments;
private $requiredCount;
private $hasAnArrayArgument = false;
private $hasOptional;
private $options;
private $shortcuts;
/**
* Constructor.
*
* @param array $definition An array of InputArgument and InputOption instance
*/
public function __construct(array $definition = array())
{
$this->setDefinition($definition);
}
/**
* Sets the definition of the input.
*
* @param array $definition The definition array
*/
public function setDefinition(array $definition)
{
$arguments = array();
$options = array();
foreach ($definition as $item) {
if ($item instanceof InputOption) {
$options[] = $item;
} else {
$arguments[] = $item;
}
}
$this->setArguments($arguments);
$this->setOptions($options);
}
/**
* Sets the InputArgument objects.
*
* @param InputArgument[] $arguments An array of InputArgument objects
*/
public function setArguments($arguments = array())
{
$this->arguments = array();
$this->requiredCount = 0;
$this->hasOptional = false;
$this->hasAnArrayArgument = false;
$this->addArguments($arguments);
}
/**
* Adds an array of InputArgument objects.
*
* @param InputArgument[] $arguments An array of InputArgument objects
*/
public function addArguments($arguments = array())
{
if (null !== $arguments) {
foreach ($arguments as $argument) {
$this->addArgument($argument);
}
}
}
/**
* Adds an InputArgument object.
*
* @param InputArgument $argument An InputArgument object
*
* @throws LogicException When incorrect argument is given
*/
public function addArgument(InputArgument $argument)
{
if (isset($this->arguments[$argument->getName()])) {
throw new LogicException(sprintf('An argument with name "%s" already exists.', $argument->getName()));
}
if ($this->hasAnArrayArgument) {
throw new LogicException('Cannot add an argument after an array argument.');
}
if ($argument->isRequired() && $this->hasOptional) {
throw new LogicException('Cannot add a required argument after an optional one.');
}
if ($argument->isArray()) {
$this->hasAnArrayArgument = true;
}
if ($argument->isRequired()) {
++$this->requiredCount;
} else {
$this->hasOptional = true;
}
$this->arguments[$argument->getName()] = $argument;
}
/**
* Returns an InputArgument by name or by position.
*
* @param string|int $name The InputArgument name or position
*
* @return InputArgument An InputArgument object
*
* @throws InvalidArgumentException When argument given doesn't exist
*/
public function getArgument($name)
{
if (!$this->hasArgument($name)) {
throw new InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name));
}
$arguments = is_int($name) ? array_values($this->arguments) : $this->arguments;
return $arguments[$name];
}
/**
* Returns true if an InputArgument object exists by name or position.
*
* @param string|int $name The InputArgument name or position
*
* @return bool true if the InputArgument object exists, false otherwise
*/
public function hasArgument($name)
{
$arguments = is_int($name) ? array_values($this->arguments) : $this->arguments;
return isset($arguments[$name]);
}
/**
* Gets the array of InputArgument objects.
*
* @return InputArgument[] An array of InputArgument objects
*/
public function getArguments()
{
return $this->arguments;
}
/**
* Returns the number of InputArguments.
*
* @return int The number of InputArguments
*/
public function getArgumentCount()
{
return $this->hasAnArrayArgument ? PHP_INT_MAX : count($this->arguments);
}
/**
* Returns the number of required InputArguments.
*
* @return int The number of required InputArguments
*/
public function getArgumentRequiredCount()
{
return $this->requiredCount;
}
/**
* Gets the default values.
*
* @return array An array of default values
*/
public function getArgumentDefaults()
{
$values = array();
foreach ($this->arguments as $argument) {
$values[$argument->getName()] = $argument->getDefault();
}
return $values;
}
/**
* Sets the InputOption objects.
*
* @param InputOption[] $options An array of InputOption objects
*/
public function setOptions($options = array())
{
$this->options = array();
$this->shortcuts = array();
$this->addOptions($options);
}
/**
* Adds an array of InputOption objects.
*
* @param InputOption[] $options An array of InputOption objects
*/
public function addOptions($options = array())
{
foreach ($options as $option) {
$this->addOption($option);
}
}
/**
* Adds an InputOption object.
*
* @param InputOption $option An InputOption object
*
* @throws LogicException When option given already exist
*/
public function addOption(InputOption $option)
{
if (isset($this->options[$option->getName()]) && !$option->equals($this->options[$option->getName()])) {
throw new LogicException(sprintf('An option named "%s" already exists.', $option->getName()));
}
if ($option->getShortcut()) {
foreach (explode('|', $option->getShortcut()) as $shortcut) {
if (isset($this->shortcuts[$shortcut]) && !$option->equals($this->options[$this->shortcuts[$shortcut]])) {
throw new LogicException(sprintf('An option with shortcut "%s" already exists.', $shortcut));
}
}
}
$this->options[$option->getName()] = $option;
if ($option->getShortcut()) {
foreach (explode('|', $option->getShortcut()) as $shortcut) {
$this->shortcuts[$shortcut] = $option->getName();
}
}
}
/**
* Returns an InputOption by name.
*
* @param string $name The InputOption name
*
* @return InputOption A InputOption object
*
* @throws InvalidArgumentException When option given doesn't exist
*/
public function getOption($name)
{
if (!$this->hasOption($name)) {
throw new InvalidArgumentException(sprintf('The "--%s" option does not exist.', $name));
}
return $this->options[$name];
}
/**
* Returns true if an InputOption object exists by name.
*
* This method can't be used to check if the user included the option when
* executing the command (use getOption() instead).
*
* @param string $name The InputOption name
*
* @return bool true if the InputOption object exists, false otherwise
*/
public function hasOption($name)
{
return isset($this->options[$name]);
}
/**
* Gets the array of InputOption objects.
*
* @return InputOption[] An array of InputOption objects
*/
public function getOptions()
{
return $this->options;
}
/**
* Returns true if an InputOption object exists by shortcut.
*
* @param string $name The InputOption shortcut
*
* @return bool true if the InputOption object exists, false otherwise
*/
public function hasShortcut($name)
{
return isset($this->shortcuts[$name]);
}
/**
* Gets an InputOption by shortcut.
*
* @param string $shortcut the Shortcut name
*
* @return InputOption An InputOption object
*/
public function getOptionForShortcut($shortcut)
{
return $this->getOption($this->shortcutToName($shortcut));
}
/**
* Gets an array of default values.
*
* @return array An array of all default values
*/
public function getOptionDefaults()
{
$values = array();
foreach ($this->options as $option) {
$values[$option->getName()] = $option->getDefault();
}
return $values;
}
/**
* Returns the InputOption name given a shortcut.
*
* @param string $shortcut The shortcut
*
* @return string The InputOption name
*
* @throws InvalidArgumentException When option given does not exist
*/
private function shortcutToName($shortcut)
{
if (!isset($this->shortcuts[$shortcut])) {
throw new InvalidArgumentException(sprintf('The "-%s" option does not exist.', $shortcut));
}
return $this->shortcuts[$shortcut];
}
/**
* Gets the synopsis.
*
* @param bool $short Whether to return the short version (with options folded) or not
*
* @return string The synopsis
*/
public function getSynopsis($short = false)
{
$elements = array();
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.' ('.$element.')';
}
if ($argument->isArray()) {
$element .= '...';
}
$elements[] = $element;
}
return implode(' ', $elements);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Input;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\RuntimeException;
/**
* InputInterface is the interface implemented by all input classes.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
interface InputInterface
{
/**
* Returns the first argument from the raw parameters (not parsed).
*
* @return string The value of the first argument or null otherwise
*/
public function getFirstArgument();
/**
* Returns true if the raw parameters (not parsed) contain a value.
*
* This method is to be used to introspect the input parameters
* before they have been validated. It must be used carefully.
*
* @param string|array $values The values to look for in the raw parameters (can be an array)
* @param bool $onlyParams Only check real parameters, skip those following an end of options (--) signal
*
* @return bool true if the value is contained in the raw parameters
*/
public function hasParameterOption($values, $onlyParams = false);
/**
* Returns the value of a raw option (not parsed).
*
* This method is to be used to introspect the input parameters
* before they have been validated. It must be used carefully.
*
* @param string|array $values The value(s) to look for in the raw parameters (can be an array)
* @param mixed $default The default value to return if no result is found
* @param bool $onlyParams Only check real parameters, skip those following an end of options (--) signal
*
* @return mixed The option value
*/
public function getParameterOption($values, $default = false, $onlyParams = false);
/**
* Binds the current Input instance with the given arguments and options.
*
* @param InputDefinition $definition A InputDefinition instance
*/
public function bind(InputDefinition $definition);
/**
* Validates the input.
*
* @throws RuntimeException When not enough arguments are given
*/
public function validate();
/**
* Returns all the given arguments merged with the default values.
*
* @return array
*/
public function getArguments();
/**
* Returns the argument value for a given argument name.
*
* @param string $name The argument name
*
* @return mixed The argument value
*
* @throws InvalidArgumentException When argument given doesn't exist
*/
public function getArgument($name);
/**
* Sets an argument value by name.
*
* @param string $name The argument name
* @param string $value The argument value
*
* @throws InvalidArgumentException When argument given doesn't exist
*/
public function setArgument($name, $value);
/**
* Returns true if an InputArgument object exists by name or position.
*
* @param string|int $name The InputArgument name or position
*
* @return bool true if the InputArgument object exists, false otherwise
*/
public function hasArgument($name);
/**
* Returns all the given options merged with the default values.
*
* @return array
*/
public function getOptions();
/**
* Returns the option value for a given option name.
*
* @param string $name The option name
*
* @return mixed The option value
*
* @throws InvalidArgumentException When option given doesn't exist
*/
public function getOption($name);
/**
* Sets an option value by name.
*
* @param string $name The option name
* @param string|bool $value The option value
*
* @throws InvalidArgumentException When option given doesn't exist
*/
public function setOption($name, $value);
/**
* Returns true if an InputOption object exists by name.
*
* @param string $name The InputOption name
*
* @return bool true if the InputOption object exists, false otherwise
*/
public function hasOption($name);
/**
* Is this input means interactive?
*
* @return bool
*/
public function isInteractive();
/**
* Sets the input interactivity.
*
* @param bool $interactive If the input should be interactive
*/
public function setInteractive($interactive);
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Input;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\LogicException;
/**
* Represents a command line option.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class InputOption
{
const VALUE_NONE = 1;
const VALUE_REQUIRED = 2;
const VALUE_OPTIONAL = 4;
const VALUE_IS_ARRAY = 8;
private $name;
private $shortcut;
private $mode;
private $default;
private $description;
/**
* Constructor.
*
* @param string $name The option name
* @param string|array $shortcut The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts
* @param int $mode The option mode: One of the VALUE_* constants
* @param string $description A description text
* @param mixed $default The default value (must be null for self::VALUE_NONE)
*
* @throws InvalidArgumentException If option mode is invalid or incompatible
*/
public function __construct($name, $shortcut = null, $mode = null, $description = '', $default = null)
{
if (0 === strpos($name, '--')) {
$name = substr($name, 2);
}
if (empty($name)) {
throw new InvalidArgumentException('An option name cannot be empty.');
}
if (empty($shortcut)) {
$shortcut = null;
}
if (null !== $shortcut) {
if (is_array($shortcut)) {
$shortcut = implode('|', $shortcut);
}
$shortcuts = preg_split('{(\|)-?}', ltrim($shortcut, '-'));
$shortcuts = array_filter($shortcuts);
$shortcut = implode('|', $shortcuts);
if (empty($shortcut)) {
throw new InvalidArgumentException('An option shortcut cannot be empty.');
}
}
if (null === $mode) {
$mode = self::VALUE_NONE;
} elseif (!is_int($mode) || $mode > 15 || $mode < 1) {
throw new InvalidArgumentException(sprintf('Option mode "%s" is not valid.', $mode));
}
$this->name = $name;
$this->shortcut = $shortcut;
$this->mode = $mode;
$this->description = $description;
if ($this->isArray() && !$this->acceptValue()) {
throw new InvalidArgumentException('Impossible to have an option mode VALUE_IS_ARRAY if the option does not accept a value.');
}
$this->setDefault($default);
}
/**
* Returns the option shortcut.
*
* @return string The shortcut
*/
public function getShortcut()
{
return $this->shortcut;
}
/**
* Returns the option name.
*
* @return string The name
*/
public function getName()
{
return $this->name;
}
/**
* Returns true if the option accepts a value.
*
* @return bool true if value mode is not self::VALUE_NONE, false otherwise
*/
public function acceptValue()
{
return $this->isValueRequired() || $this->isValueOptional();
}
/**
* Returns true if the option requires a value.
*
* @return bool true if value mode is self::VALUE_REQUIRED, false otherwise
*/
public function isValueRequired()
{
return self::VALUE_REQUIRED === (self::VALUE_REQUIRED & $this->mode);
}
/**
* Returns true if the option takes an optional value.
*
* @return bool true if value mode is self::VALUE_OPTIONAL, false otherwise
*/
public function isValueOptional()
{
return self::VALUE_OPTIONAL === (self::VALUE_OPTIONAL & $this->mode);
}
/**
* Returns true if the option can take multiple values.
*
* @return bool true if mode is self::VALUE_IS_ARRAY, false otherwise
*/
public function isArray()
{
return self::VALUE_IS_ARRAY === (self::VALUE_IS_ARRAY & $this->mode);
}
/**
* Sets the default value.
*
* @param mixed $default The default value
*
* @throws LogicException When incorrect default value is given
*/
public function setDefault($default = null)
{
if (self::VALUE_NONE === (self::VALUE_NONE & $this->mode) && null !== $default) {
throw new LogicException('Cannot set a default value when using InputOption::VALUE_NONE mode.');
}
if ($this->isArray()) {
if (null === $default) {
$default = array();
} elseif (!is_array($default)) {
throw new LogicException('A default value for an array option must be an array.');
}
}
$this->default = $this->acceptValue() ? $default : false;
}
/**
* Returns the default value.
*
* @return mixed The default value
*/
public function getDefault()
{
return $this->default;
}
/**
* Returns the description text.
*
* @return string The description text
*/
public function getDescription()
{
return $this->description;
}
/**
* Checks whether the given option equals this one.
*
* @param InputOption $option option to compare
*
* @return bool
*/
public function equals(InputOption $option)
{
return $option->getName() === $this->getName()
&& $option->getShortcut() === $this->getShortcut()
&& $option->getDefault() === $this->getDefault()
&& $option->isArray() === $this->isArray()
&& $option->isValueRequired() === $this->isValueRequired()
&& $option->isValueOptional() === $this->isValueOptional()
;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Input;
use Symfony\Component\Console\Exception\InvalidArgumentException;
/**
* StringInput represents an input provided as a string.
*
* Usage:
*
* $input = new StringInput('foo --bar="foobar"');
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class StringInput extends ArgvInput
{
const REGEX_STRING = '([^\s]+?)(?:\s|(?<!\\\\)"|(?<!\\\\)\'|$)';
const REGEX_QUOTED_STRING = '(?:"([^"\\\\]*(?:\\\\.[^"\\\\]*)*)"|\'([^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\')';
/**
* Constructor.
*
* @param string $input An array of parameters from the CLI (in the argv format)
*/
public function __construct($input)
{
parent::__construct(array());
$this->setTokens($this->tokenize($input));
}
/**
* Tokenizes a string.
*
* @param string $input The input to tokenize
*
* @return array An array of tokens
*
* @throws InvalidArgumentException When unable to parse input (should never happen)
*/
private function tokenize($input)
{
$tokens = array();
$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(array('"\'', '\'"', '\'\'', '""'), '', substr($match[3], 1, strlen($match[3]) - 2)));
} elseif (preg_match('/'.self::REGEX_QUOTED_STRING.'/A', $input, $match, null, $cursor)) {
$tokens[] = stripcslashes(substr($match[0], 1, strlen($match[0]) - 2));
} elseif (preg_match('/'.self::REGEX_STRING.'/A', $input, $match, null, $cursor)) {
$tokens[] = stripcslashes($match[1]);
} else {
// should never happen
throw new InvalidArgumentException(sprintf('Unable to parse input near "... %s ..."', substr($input, $cursor, 10)));
}
$cursor += strlen($match[0]);
}
return $tokens;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Logger;
use Psr\Log\AbstractLogger;
use Psr\Log\InvalidArgumentException;
use Psr\Log\LogLevel;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
/**
* PSR-3 compliant console logger.
*
* @author Kévin Dunglas <dunglas@gmail.com>
*
* @link http://www.php-fig.org/psr/psr-3/
*/
class ConsoleLogger extends AbstractLogger
{
const INFO = 'info';
const ERROR = 'error';
/**
* @var OutputInterface
*/
private $output;
/**
* @var array
*/
private $verbosityLevelMap = array(
LogLevel::EMERGENCY => OutputInterface::VERBOSITY_NORMAL,
LogLevel::ALERT => OutputInterface::VERBOSITY_NORMAL,
LogLevel::CRITICAL => OutputInterface::VERBOSITY_NORMAL,
LogLevel::ERROR => OutputInterface::VERBOSITY_NORMAL,
LogLevel::WARNING => OutputInterface::VERBOSITY_NORMAL,
LogLevel::NOTICE => OutputInterface::VERBOSITY_VERBOSE,
LogLevel::INFO => OutputInterface::VERBOSITY_VERY_VERBOSE,
LogLevel::DEBUG => OutputInterface::VERBOSITY_DEBUG,
);
/**
* @var array
*/
private $formatLevelMap = array(
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,
);
/**
* @param OutputInterface $output
* @param array $verbosityLevelMap
* @param array $formatLevelMap
*/
public function __construct(OutputInterface $output, array $verbosityLevelMap = array(), array $formatLevelMap = array())
{
$this->output = $output;
$this->verbosityLevelMap = $verbosityLevelMap + $this->verbosityLevelMap;
$this->formatLevelMap = $formatLevelMap + $this->formatLevelMap;
}
/**
* {@inheritdoc}
*/
public function log($level, $message, array $context = array())
{
if (!isset($this->verbosityLevelMap[$level])) {
throw new InvalidArgumentException(sprintf('The log level "%s" does not exist.', $level));
}
// Write to the error output if necessary and available
if ($this->formatLevelMap[$level] === self::ERROR && $this->output instanceof ConsoleOutputInterface) {
$output = $this->output->getErrorOutput();
} else {
$output = $this->output;
}
if ($output->getVerbosity() >= $this->verbosityLevelMap[$level]) {
$output->writeln(sprintf('<%1$s>[%2$s] %3$s</%1$s>', $this->formatLevelMap[$level], $level, $this->interpolate($message, $context)));
}
}
/**
* Interpolates context values into the message placeholders.
*
* @author PHP Framework Interoperability Group
*
* @param string $message
* @param array $context
*
* @return string
*/
private function interpolate($message, array $context)
{
// build a replacement array with braces around the context keys
$replace = array();
foreach ($context as $key => $val) {
if (!is_array($val) && (!is_object($val) || method_exists($val, '__toString'))) {
$replace[sprintf('{%s}', $key)] = $val;
}
}
// interpolate replacement values into the message and return
return strtr($message, $replace);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Output;
/**
* @author Jean-François Simon <contact@jfsimon.fr>
*/
class BufferedOutput extends Output
{
/**
* @var string
*/
private $buffer = '';
/**
* Empties buffer and returns its content.
*
* @return string
*/
public function fetch()
{
$content = $this->buffer;
$this->buffer = '';
return $content;
}
/**
* {@inheritdoc}
*/
protected function doWrite($message, $newline)
{
$this->buffer .= $message;
if ($newline) {
$this->buffer .= "\n";
}
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Output;
use Symfony\Component\Console\Formatter\OutputFormatterInterface;
/**
* ConsoleOutput is the default class for all CLI output. It uses STDOUT and STDERR.
*
* This class is a convenient wrapper around `StreamOutput` for both STDOUT and STDERR.
*
* $output = new ConsoleOutput();
*
* This is equivalent to:
*
* $output = new StreamOutput(fopen('php://stdout', 'w'));
* $stdErr = new StreamOutput(fopen('php://stderr', 'w'));
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class ConsoleOutput extends StreamOutput implements ConsoleOutputInterface
{
/**
* @var StreamOutput
*/
private $stderr;
/**
* Constructor.
*
* @param int $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface)
* @param bool|null $decorated Whether to decorate messages (null for auto-guessing)
* @param OutputFormatterInterface|null $formatter Output formatter instance (null to use default OutputFormatter)
*/
public function __construct($verbosity = self::VERBOSITY_NORMAL, $decorated = null, OutputFormatterInterface $formatter = null)
{
parent::__construct($this->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());
}
}
/**
* {@inheritdoc}
*/
public function setDecorated($decorated)
{
parent::setDecorated($decorated);
$this->stderr->setDecorated($decorated);
}
/**
* {@inheritdoc}
*/
public function setFormatter(OutputFormatterInterface $formatter)
{
parent::setFormatter($formatter);
$this->stderr->setFormatter($formatter);
}
/**
* {@inheritdoc}
*/
public function setVerbosity($level)
{
parent::setVerbosity($level);
$this->stderr->setVerbosity($level);
}
/**
* {@inheritdoc}
*/
public function getErrorOutput()
{
return $this->stderr;
}
/**
* {@inheritdoc}
*/
public function setErrorOutput(OutputInterface $error)
{
$this->stderr = $error;
}
/**
* Returns true if current environment supports writing console output to
* STDOUT.
*
* @return bool
*/
protected function hasStdoutSupport()
{
return false === $this->isRunningOS400();
}
/**
* Returns true if current environment supports writing console output to
* STDERR.
*
* @return bool
*/
protected function hasStderrSupport()
{
return false === $this->isRunningOS400();
}
/**
* Checks if current executing environment is IBM iSeries (OS400), which
* doesn't properly convert character-encodings between ASCII to EBCDIC.
*
* @return bool
*/
private function isRunningOS400()
{
$checks = array(
function_exists('php_uname') ? php_uname('s') : '',
getenv('OSTYPE'),
PHP_OS,
);
return false !== stripos(implode(';', $checks), 'OS400');
}
/**
* @return resource
*/
private function openOutputStream()
{
if (!$this->hasStdoutSupport()) {
return fopen('php://output', 'w');
}
return @fopen('php://stdout', 'w') ?: fopen('php://output', 'w');
}
/**
* @return resource
*/
private function openErrorStream()
{
return fopen($this->hasStderrSupport() ? 'php://stderr' : 'php://output', 'w');
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Output;
/**
* ConsoleOutputInterface is the interface implemented by ConsoleOutput class.
* This adds information about stderr output stream.
*
* @author Dariusz Górecki <darek.krk@gmail.com>
*/
interface ConsoleOutputInterface extends OutputInterface
{
/**
* Gets the OutputInterface for errors.
*
* @return OutputInterface
*/
public function getErrorOutput();
/**
* Sets the OutputInterface used for errors.
*
* @param OutputInterface $error
*/
public function setErrorOutput(OutputInterface $error);
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Output;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Formatter\OutputFormatterInterface;
/**
* NullOutput suppresses all output.
*
* $output = new NullOutput();
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Tobias Schultze <http://tobion.de>
*/
class NullOutput implements OutputInterface
{
/**
* {@inheritdoc}
*/
public function setFormatter(OutputFormatterInterface $formatter)
{
// do nothing
}
/**
* {@inheritdoc}
*/
public function getFormatter()
{
// to comply with the interface we must return a OutputFormatterInterface
return new OutputFormatter();
}
/**
* {@inheritdoc}
*/
public function setDecorated($decorated)
{
// do nothing
}
/**
* {@inheritdoc}
*/
public function isDecorated()
{
return false;
}
/**
* {@inheritdoc}
*/
public function setVerbosity($level)
{
// do nothing
}
/**
* {@inheritdoc}
*/
public function getVerbosity()
{
return self::VERBOSITY_QUIET;
}
/**
* {@inheritdoc}
*/
public function isQuiet()
{
return true;
}
/**
* {@inheritdoc}
*/
public function isVerbose()
{
return false;
}
/**
* {@inheritdoc}
*/
public function isVeryVerbose()
{
return false;
}
/**
* {@inheritdoc}
*/
public function isDebug()
{
return false;
}
/**
* {@inheritdoc}
*/
public function writeln($messages, $options = self::OUTPUT_NORMAL)
{
// do nothing
}
/**
* {@inheritdoc}
*/
public function write($messages, $newline = false, $options = self::OUTPUT_NORMAL)
{
// do nothing
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Output;
use Symfony\Component\Console\Formatter\OutputFormatterInterface;
use Symfony\Component\Console\Formatter\OutputFormatter;
/**
* Base class for output classes.
*
* There are five levels of verbosity:
*
* * normal: no option passed (normal output)
* * verbose: -v (more output)
* * very verbose: -vv (highly extended output)
* * debug: -vvv (all debug output)
* * quiet: -q (no output)
*
* @author Fabien Potencier <fabien@symfony.com>
*/
abstract class Output implements OutputInterface
{
private $verbosity;
private $formatter;
/**
* Constructor.
*
* @param int $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface)
* @param bool $decorated Whether to decorate messages
* @param OutputFormatterInterface|null $formatter Output formatter instance (null to use default OutputFormatter)
*/
public function __construct($verbosity = self::VERBOSITY_NORMAL, $decorated = false, OutputFormatterInterface $formatter = null)
{
$this->verbosity = null === $verbosity ? self::VERBOSITY_NORMAL : $verbosity;
$this->formatter = $formatter ?: new OutputFormatter();
$this->formatter->setDecorated($decorated);
}
/**
* {@inheritdoc}
*/
public function setFormatter(OutputFormatterInterface $formatter)
{
$this->formatter = $formatter;
}
/**
* {@inheritdoc}
*/
public function getFormatter()
{
return $this->formatter;
}
/**
* {@inheritdoc}
*/
public function setDecorated($decorated)
{
$this->formatter->setDecorated($decorated);
}
/**
* {@inheritdoc}
*/
public function isDecorated()
{
return $this->formatter->isDecorated();
}
/**
* {@inheritdoc}
*/
public function setVerbosity($level)
{
$this->verbosity = (int) $level;
}
/**
* {@inheritdoc}
*/
public function getVerbosity()
{
return $this->verbosity;
}
/**
* {@inheritdoc}
*/
public function isQuiet()
{
return self::VERBOSITY_QUIET === $this->verbosity;
}
/**
* {@inheritdoc}
*/
public function isVerbose()
{
return self::VERBOSITY_VERBOSE <= $this->verbosity;
}
/**
* {@inheritdoc}
*/
public function isVeryVerbose()
{
return self::VERBOSITY_VERY_VERBOSE <= $this->verbosity;
}
/**
* {@inheritdoc}
*/
public function isDebug()
{
return self::VERBOSITY_DEBUG <= $this->verbosity;
}
/**
* {@inheritdoc}
*/
public function writeln($messages, $options = self::OUTPUT_NORMAL)
{
$this->write($messages, true, $options);
}
/**
* {@inheritdoc}
*/
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);
}
}
/**
* Writes a message to the output.
*
* @param string $message A message to write to the output
* @param bool $newline Whether to add a newline or not
*/
abstract protected function doWrite($message, $newline);
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Output;
use Symfony\Component\Console\Formatter\OutputFormatterInterface;
/**
* OutputInterface is the interface implemented by all Output classes.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
interface OutputInterface
{
const VERBOSITY_QUIET = 16;
const VERBOSITY_NORMAL = 32;
const VERBOSITY_VERBOSE = 64;
const VERBOSITY_VERY_VERBOSE = 128;
const VERBOSITY_DEBUG = 256;
const OUTPUT_NORMAL = 1;
const OUTPUT_RAW = 2;
const OUTPUT_PLAIN = 4;
/**
* Writes a message to the output.
*
* @param string|array $messages The message as an array of lines or a single string
* @param bool $newline Whether to add a newline
* @param int $options A bitmask of options (one of the OUTPUT or VERBOSITY constants), 0 is considered the same as self::OUTPUT_NORMAL | self::VERBOSITY_NORMAL
*/
public function write($messages, $newline = false, $options = 0);
/**
* Writes a message to the output and adds a newline at the end.
*
* @param string|array $messages The message as an array of lines of a single string
* @param int $options A bitmask of options (one of the OUTPUT or VERBOSITY constants), 0 is considered the same as self::OUTPUT_NORMAL | self::VERBOSITY_NORMAL
*/
public function writeln($messages, $options = 0);
/**
* Sets the verbosity of the output.
*
* @param int $level The level of verbosity (one of the VERBOSITY constants)
*/
public function setVerbosity($level);
/**
* Gets the current verbosity of the output.
*
* @return int The current level of verbosity (one of the VERBOSITY constants)
*/
public function getVerbosity();
/**
* Returns whether verbosity is quiet (-q).
*
* @return bool true if verbosity is set to VERBOSITY_QUIET, false otherwise
*/
public function isQuiet();
/**
* Returns whether verbosity is verbose (-v).
*
* @return bool true if verbosity is set to VERBOSITY_VERBOSE, false otherwise
*/
public function isVerbose();
/**
* Returns whether verbosity is very verbose (-vv).
*
* @return bool true if verbosity is set to VERBOSITY_VERY_VERBOSE, false otherwise
*/
public function isVeryVerbose();
/**
* Returns whether verbosity is debug (-vvv).
*
* @return bool true if verbosity is set to VERBOSITY_DEBUG, false otherwise
*/
public function isDebug();
/**
* Sets the decorated flag.
*
* @param bool $decorated Whether to decorate the messages
*/
public function setDecorated($decorated);
/**
* Gets the decorated flag.
*
* @return bool true if the output will decorate messages, false otherwise
*/
public function isDecorated();
/**
* Sets output formatter.
*
* @param OutputFormatterInterface $formatter
*/
public function setFormatter(OutputFormatterInterface $formatter);
/**
* Returns current output formatter instance.
*
* @return OutputFormatterInterface
*/
public function getFormatter();
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Output;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\Console\Formatter\OutputFormatterInterface;
/**
* StreamOutput writes the output to a given stream.
*
* Usage:
*
* $output = new StreamOutput(fopen('php://stdout', 'w'));
*
* As `StreamOutput` can use any stream, you can also use a file:
*
* $output = new StreamOutput(fopen('/path/to/output.log', 'a', false));
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class StreamOutput extends Output
{
private $stream;
/**
* Constructor.
*
* @param resource $stream A stream resource
* @param int $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface)
* @param bool|null $decorated Whether to decorate messages (null for auto-guessing)
* @param OutputFormatterInterface|null $formatter Output formatter instance (null to use default OutputFormatter)
*
* @throws InvalidArgumentException When first argument is not a real stream
*/
public function __construct($stream, $verbosity = self::VERBOSITY_NORMAL, $decorated = null, OutputFormatterInterface $formatter = null)
{
if (!is_resource($stream) || 'stream' !== get_resource_type($stream)) {
throw new InvalidArgumentException('The StreamOutput class needs a stream as its first argument.');
}
$this->stream = $stream;
if (null === $decorated) {
$decorated = $this->hasColorSupport();
}
parent::__construct($verbosity, $decorated, $formatter);
}
/**
* Gets the stream attached to this StreamOutput instance.
*
* @return resource A stream resource
*/
public function getStream()
{
return $this->stream;
}
/**
* {@inheritdoc}
*/
protected function doWrite($message, $newline)
{
if (false === @fwrite($this->stream, $message) || ($newline && (false === @fwrite($this->stream, PHP_EOL)))) {
// should never happen
throw new RuntimeException('Unable to write output.');
}
fflush($this->stream);
}
/**
* Returns true if the stream supports colorization.
*
* Colorization is disabled if not supported by the stream:
*
* - Windows != 10.0.10586 without Ansicon, ConEmu or Mintty
* - non tty consoles
*
* @return bool true if the stream supports colorization, false otherwise
*/
protected function hasColorSupport()
{
if (DIRECTORY_SEPARATOR === '\\') {
return
'10.0.10586' === PHP_WINDOWS_VERSION_MAJOR.'.'.PHP_WINDOWS_VERSION_MINOR.'.'.PHP_WINDOWS_VERSION_BUILD
|| false !== getenv('ANSICON')
|| 'ON' === getenv('ConEmuANSI')
|| 'xterm' === getenv('TERM');
}
return function_exists('posix_isatty') && @posix_isatty($this->stream);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Question;
use Symfony\Component\Console\Exception\InvalidArgumentException;
/**
* Represents a choice question.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class ChoiceQuestion extends Question
{
private $choices;
private $multiselect = false;
private $prompt = ' > ';
private $errorMessage = 'Value "%s" is invalid';
/**
* Constructor.
*
* @param string $question The question to ask to the user
* @param array $choices The list of available choices
* @param mixed $default The default answer to return
*/
public function __construct($question, array $choices, $default = null)
{
parent::__construct($question, $default);
$this->choices = $choices;
$this->setValidator($this->getDefaultValidator());
$this->setAutocompleterValues($choices);
}
/**
* Returns available choices.
*
* @return array
*/
public function getChoices()
{
return $this->choices;
}
/**
* Sets multiselect option.
*
* When multiselect is set to true, multiple choices can be answered.
*
* @param bool $multiselect
*
* @return ChoiceQuestion The current instance
*/
public function setMultiselect($multiselect)
{
$this->multiselect = $multiselect;
$this->setValidator($this->getDefaultValidator());
return $this;
}
/**
* Returns whether the choices are multiselect.
*
* @return bool
*/
public function isMultiselect()
{
return $this->multiselect;
}
/**
* Gets the prompt for choices.
*
* @return string
*/
public function getPrompt()
{
return $this->prompt;
}
/**
* Sets the prompt for choices.
*
* @param string $prompt
*
* @return ChoiceQuestion The current instance
*/
public function setPrompt($prompt)
{
$this->prompt = $prompt;
return $this;
}
/**
* Sets the error message for invalid values.
*
* The error message has a string placeholder (%s) for the invalid value.
*
* @param string $errorMessage
*
* @return ChoiceQuestion The current instance
*/
public function setErrorMessage($errorMessage)
{
$this->errorMessage = $errorMessage;
$this->setValidator($this->getDefaultValidator());
return $this;
}
/**
* Returns the default answer validator.
*
* @return callable
*/
private function getDefaultValidator()
{
$choices = $this->choices;
$errorMessage = $this->errorMessage;
$multiselect = $this->multiselect;
$isAssoc = $this->isAssoc($choices);
return function ($selected) use ($choices, $errorMessage, $multiselect, $isAssoc) {
// Collapse all spaces.
$selectedChoices = str_replace(' ', '', $selected);
if ($multiselect) {
// Check for a separated comma values
if (!preg_match('/^[a-zA-Z0-9_-]+(?:,[a-zA-Z0-9_-]+)*$/', $selectedChoices, $matches)) {
throw new InvalidArgumentException(sprintf($errorMessage, $selected));
}
$selectedChoices = explode(',', $selectedChoices);
} else {
$selectedChoices = array($selected);
}
$multiselectChoices = array();
foreach ($selectedChoices as $value) {
$results = array();
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);
};
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Question;
/**
* Represents a yes/no question.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class ConfirmationQuestion extends Question
{
private $trueAnswerRegex;
/**
* Constructor.
*
* @param string $question The question to ask to the user
* @param bool $default The default answer to return, true or false
* @param string $trueAnswerRegex A regex to match the "yes" answer
*/
public function __construct($question, $default = true, $trueAnswerRegex = '/^y/i')
{
parent::__construct($question, (bool) $default);
$this->trueAnswerRegex = $trueAnswerRegex;
$this->setNormalizer($this->getDefaultNormalizer());
}
/**
* Returns the default answer normalizer.
*
* @return callable
*/
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;
};
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Question;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\LogicException;
/**
* Represents a Question.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class Question
{
private $question;
private $attempts;
private $hidden = false;
private $hiddenFallback = true;
private $autocompleterValues;
private $validator;
private $default;
private $normalizer;
/**
* Constructor.
*
* @param string $question The question to ask to the user
* @param mixed $default The default answer to return if the user enters nothing
*/
public function __construct($question, $default = null)
{
$this->question = $question;
$this->default = $default;
}
/**
* Returns the question.
*
* @return string
*/
public function getQuestion()
{
return $this->question;
}
/**
* Returns the default answer.
*
* @return mixed
*/
public function getDefault()
{
return $this->default;
}
/**
* Returns whether the user response must be hidden.
*
* @return bool
*/
public function isHidden()
{
return $this->hidden;
}
/**
* Sets whether the user response must be hidden or not.
*
* @param bool $hidden
*
* @return Question The current instance
*
* @throws LogicException In case the autocompleter is also used
*/
public function setHidden($hidden)
{
if ($this->autocompleterValues) {
throw new LogicException('A hidden question cannot use the autocompleter.');
}
$this->hidden = (bool) $hidden;
return $this;
}
/**
* In case the response can not be hidden, whether to fallback on non-hidden question or not.
*
* @return bool
*/
public function isHiddenFallback()
{
return $this->hiddenFallback;
}
/**
* Sets whether to fallback on non-hidden question if the response can not be hidden.
*
* @param bool $fallback
*
* @return Question The current instance
*/
public function setHiddenFallback($fallback)
{
$this->hiddenFallback = (bool) $fallback;
return $this;
}
/**
* Gets values for the autocompleter.
*
* @return null|array|\Traversable
*/
public function getAutocompleterValues()
{
return $this->autocompleterValues;
}
/**
* Sets values for the autocompleter.
*
* @param null|array|\Traversable $values
*
* @return Question The current instance
*
* @throws InvalidArgumentException
* @throws LogicException
*/
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)) {
if (!$values instanceof \Traversable || !$values instanceof \Countable) {
throw new InvalidArgumentException('Autocompleter values can be either an array, `null` or an object implementing both `Countable` and `Traversable` interfaces.');
}
}
if ($this->hidden) {
throw new LogicException('A hidden question cannot use the autocompleter.');
}
$this->autocompleterValues = $values;
return $this;
}
/**
* Sets a validator for the question.
*
* @param null|callable $validator
*
* @return Question The current instance
*/
public function setValidator(callable $validator = null)
{
$this->validator = $validator;
return $this;
}
/**
* Gets the validator for the question.
*
* @return null|callable
*/
public function getValidator()
{
return $this->validator;
}
/**
* Sets the maximum number of attempts.
*
* Null means an unlimited number of attempts.
*
* @param null|int $attempts
*
* @return Question The current instance
*
* @throws InvalidArgumentException In case the number of attempts is invalid.
*/
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;
}
/**
* Gets the maximum number of attempts.
*
* Null means an unlimited number of attempts.
*
* @return null|int
*/
public function getMaxAttempts()
{
return $this->attempts;
}
/**
* Sets a normalizer for the response.
*
* The normalizer can be a callable (a string), a closure or a class implementing __invoke.
*
* @param callable $normalizer
*
* @return Question The current instance
*/
public function setNormalizer(callable $normalizer)
{
$this->normalizer = $normalizer;
return $this;
}
/**
* Gets the normalizer for the response.
*
* The normalizer can ba a callable (a string), a closure or a class implementing __invoke.
*
* @return callable
*/
public function getNormalizer()
{
return $this->normalizer;
}
protected function isAssoc($array)
{
return (bool) count(array_filter(array_keys($array), 'is_string'));
}
}
MZ<EFBFBD>ÿÿ¸@躴 Í!¸LÍ!This program cannot be run in DOS mode.
$Æ,Í;§Bž;§Bž;§Bž2ßמ:§Bž2ßÁž-§Bž2߯ž9§Bž2ßÑž?§Bža9ž8§Bž;§Cž§Bž2ßÈž:§Bž2ßÖž:§Bž2ßÓž:§BžRich;§BžPEL¬MoOà 
8 @`@<40><00>"P@ Pp!8!@ Ø.text 
 `.rdataÎ
@@.data<00>0@À.rsrc @@@.relocÌP"@Bj$¸æ@èxjöÿ @ƒeÐð<E280B9>EÐPVÿ @EЃàûPVÿ @<00>MÔÿX @ƒeü<00>EÔPÿ5H @ÿL @YYÿ5\ @<00>EÔPÿ5` @ÿD @YYÈÿP @ƒMüÿ<C3BC>MÔÿT @3ÀèHÃ; 0@uóÃé¬h€@裡l3@Ç$40@ÿ5h3@£40@h$0@h(0@h 0@ÿ  @ƒÄ£00@…À}jè¹YÃjh"@è3Û‰]üd¡p‰]ä¿€3@SVWÿ0 @;Ãt;Æu3öF‰uäëÿ4 @ëÚ3öF¡|3@;Æu
jè\Yë;¡|3@…Àu,‰5|3@hð @hä @è§YY…ÀtÇEüþÿÿÿ¸ÿé݉5<0@¡|3@;Æuhà @hØ @èlYYÇ|3@9]äuSWÿ8 @9Œ3@thŒ3@èƒY…Àt
SjSÿŒ3@¡$0@ ¼ @ÿ5$0@ÿ5(0@ÿ5 0@èþÿÿƒÄ £80@9,0@u7PÿÀ @ ‰MàPQèŽYYÃEà£80@3Û9,0@uPÿh @9<0@uÿœ @ÇEüþÿÿÿ¡80@èûøMZf9@t3ÀëM¡<@<00>@<00>8PE·H<18>ù t<1B>ù uÕƒ¸vÌ3É9ˆøëƒxtv¼3É9ˆè•ÁÁj£,0@ÿp @jÿÿl @YY£„3@£ˆ3@ÿÌ @ t3@ÿˆ @ p3@¡¨ @£x3@èV謃=0@u @ÿ¬ @Yègƒ=0@ÿu jÿÿ° @Y3ÀÃè{éŸýÿÿÿUì<E280B9>ì(£H1@‰ D1@@1@<1@‰581@‰=41@`1@fŒ T1@01@,1@fŒ%(1@fŒ-$1@œ<>X1@E£L1@E£P1@<00>E£\1@‹…àüÿÿǘ0@¡P1@£L0@Ç@0@ ÀÇD0@¡0@‰…Øüÿÿ¡0@‰…Üüÿÿÿ @£<>0@jè?Yjÿ @h!@ÿ$ @ƒ=<3D>0@ujèYh Àÿ( @Pÿ, @ÉÃÿUìE<00>8csmàu*ƒxu$@= t=!t="t=@™uèÐ3À]ÂhH@ÿ @3ÀÃÿ%¤ @jh("@èbÿ5ˆ3@5Œ @ÿÖY‰Eäƒøÿu ÿuÿÄ @Yëgjè’Yƒeüÿ5ˆ3@ÿÖ‰Eäÿ5„3@ÿÖYY‰Eà<45>EàP<C3A0>EäPÿu5l @ÿÖYPèU‰EÜÿuäÿÖ£ˆ3@ÿuàÿփģ„3@ÇEüþÿÿÿè EÜèÃjèÿUìÿuèNÿÿÿ÷ØÀ÷ØYH]ÃÿV¸ü!@¾ü!@Wø;Æs…ÀtÿЃÇ;þrñ_^ÃÿV¸"@¾"@Wø;Æs…ÀtÿЃÇ;þrñ_^Ãÿ%È @ÌÌÌÌÿUìM¸MZf9t3À]ÃA<Á<>8PEuï3Ò¹ f9H”‹Â]ÃÌÌÌÌÌÌÌÌÌÌÌÿUìEH<È·ASV·q3ÒW<C392>D…öv} H ;ùr XÙ;ûr
BƒÀ(;Örè3À_^[]ÃÌÌÌÌÌÌÌÌÌÌÌÌÿUìjþhH"@he@PƒìSVW¡0@1Eø3ÅP<C385>Eðd£‰eèÇEüh@è*ÿÿÿƒÄ…ÀtUE-@Ph@èPÿÿÿƒÄ…Àt;@$Áè÷ЃàÇEüþÿÿÿMðd‰ Y_^[‹å]Ã3Ò=À”ÂÂÃeèÇEüþÿÿÿ3ÀMðd‰ Y_^[‹å]ÃÌÿ%¸ @ÿ%´ @ÌÌhe@dÿ5D$‰l$<10>l$+àSVW¡0@1Eü3ÅP‰eèÿuøEüÇEüþÿÿÿ‰Eø<45>Eðd£ÃMðd‰ Y__^[‹å]QÃÿUìÿuÿuÿu ÿuh‡@h0@èçƒÄÿVhh3öVèÙƒÄ …Àt VVVVVèƒÄ^Ã3ÀÃÿUìƒì¡0@ƒeøƒeüSW¿Næ@»»ÿÿ;Çt …Ãt ÷У0@ë`V<>EøPÿ< @uü3uøÿ @3ðÿ @3ðÿ @<33>EðPÿ @Eô3Eð3ð;÷u¾Oæ@»ë …óu‹ÆÁà ð‰50@÷Ö‰50@^_[ÉÃÿ%t @ÿ%x @ÿ%| @ÿ%€ @ÿ%„ @ÿ%<25> @ÿ%” @ÿ%˜ @ÿ%Ð @Pdÿ5<00>D$ +d$ SVW‰(‹è¡0@3ÅP‰EðÿuüÇEüÿÿÿÿ<C3BF>Eôd£ÃMôd‰ Y__^[‹å]QÃMð3Íè¯÷ÿÿéÝÿÿÿ<C3BF>MÔÿ%T @T$<08>B JÌ3Èè<C388>÷ÿÿJü3Èè†÷ÿÿ¸l"@ésÿÿÿ¸#Ê#Ü#ˆ)r)b)H)4))ú(æ(Ò(´(¬((ž)ú#à$%Ê%&d&®&¤$('Ä'Ö'è'þ'(((6(¦'H(Z(t(†('''''l'^'R'F'>'>(0'¶'¸)@W@Š@¬MoOl€!@0@˜0@bad allocationH0@ð!@RSDSÑŒ³´<>J¨!öÌëLZc:\users\seld\documents\visual studio 2010\Projects\hiddeninp\Release\hiddeninp.pdbeæþÿÿÿÐÿÿÿþÿÿÿ@@þÿÿÿÌÿÿÿþÿÿÿ:@þÿÿÿØÿÿÿþÿÿÿË@ß@ÿÿÿÿÝ@"d"@à"ì# $#ô&D H#(h ¸#Ê#Ü#ˆ)r)b)H)4))ú(æ(Ò(´(¬((ž)ú#à$%Ê%&d&®&¤$('Ä'Ö'è'þ'(((6(¦'H(Z(t(†('''''l'^'R'F'>'>(0'¶'¸)GetConsoleMode·SetConsoleMode;GetStdHandleKERNEL32.dll??$?6DU?$char_traits@D@std@@V?$allocator@D@1@@std@@YAAAV?$basic_ostream@DU?$char_traits@D@std@@@0@AAV10@ABV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@0@@Z?cout@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@AJ?cin@std@@3V?$basic_istream@DU?$char_traits@D@std@@@1@AÂ??$getline@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@YAAAV?$basic_istream@DU?$char_traits@D@std@@@0@AAV10@AAV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@0@@Z??6?$basic_ostream@DU?$char_traits@D@std@@@std@@QAEAAV01@P6AAAV01@AAV01@@Z@Z_??1?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@QAE@XZ{??0?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@QAE@XZ³?endl@std@@YAAAV?$basic_ostream@DU?$char_traits@D@std@@@1@AAV21@@ZMSVCP90.dll_amsg_exitŸ__getmainargs,_cexit|_exitf_XcptFilterÌexit __initenv_initterm_initterm_e<_configthreadlocaleã__setusermatherr _adjust_fdivË__p__commodeÏ__p__fmodej_encode_pointerà__set_app_typeK_crt_debugger_hookC?terminate@@YAXXZMSVCR90.dllæ_unlock__dllonexitv_lock_onexit`_decode_pointers_except_handler4_common _invoke_watson?_controlfp_s½InterlockedExchange!SleepºInterlockedCompareExchange-TerminateProcess©GetCurrentProcess>UnhandledExceptionFilterSetUnhandledExceptionFilterÑIsDebuggerPresentTQueryPerformanceCounterfGetTickCount­GetCurrentThreadIdªGetCurrentProcessIdOGetSystemTimeAsFileTimes__CxxFrameHandler3Næ@»±¿Dÿÿÿÿÿÿÿÿþÿÿÿ$!@ 8Ph  <00> @(äÈCVä(4VS_VERSION_INFO½ïþStringFileInfob040904b0ÊQFileDescriptionReads from stdin without leaking info to the terminal and outputs back to stdout6 FileVersion1, 0, 0, 08 InternalNamehiddeninputPLegalCopyrightJordi Boggiano - 2012HOriginalFilenamehiddeninput.exe: ProductNameHidden Input: ProductVersion1, 0, 0, 0DVarFileInfo$Translation °<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
<security>
<requestedPrivileges>
<requestedExecutionLevel level="asInvoker" uiAccess="false"></requestedExecutionLevel>
</requestedPrivileges>
</security>
</trustInfo>
<dependency>
<dependentAssembly>
<assemblyIdentity type="win32" name="Microsoft.VC90.CRT" version="9.0.21022.8" processorArchitecture="x86" publicKeyToken="1fc8b3b9a1e18e3b"></assemblyIdentity>
</dependentAssembly>
</dependency>
</assembly>PAPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDING@00!0/080F0L0T0^0d0n0{0‰0—0¡0¨0®0³0¸0½0Â0È0Ð0ä0ÿ01#1-1@1J1O1T1v1{1„1‰11§1­1´1È1Í1Ó1Û1á1ç1ô1ú12"2*23292A2M2_2j2p2¹2¿2Ç2Î2Ó2Ù2ß2ç2í2ô2û2 333%303N3T3Z3`3f3l3s3z3<7A>3ˆ3<CB86>33<E28093>3¥3­3µ3Á3Ê3Ï3Õ3ß3è3ó3ÿ34444%4;4B444š4¡4¬4²4Æ4Û4æ4þ45!5^5c5„5‰5¨5H6M6_6}66—677 7*7w7|7Á7ä7ñ7ý78 88=8E8P8V8\8b8h8n8t8z8€8œ8â89 $Ü0è0ì01 1t1x12 2@2\2`2h2t20 0<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Style;
use Symfony\Component\Console\Formatter\OutputFormatterInterface;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Decorates output to add console style guide helpers.
*
* @author Kevin Bond <kevinbond@gmail.com>
*/
abstract class OutputStyle implements OutputInterface, StyleInterface
{
private $output;
/**
* @param OutputInterface $output
*/
public function __construct(OutputInterface $output)
{
$this->output = $output;
}
/**
* {@inheritdoc}
*/
public function newLine($count = 1)
{
$this->output->write(str_repeat(PHP_EOL, $count));
}
/**
* @param int $max
*
* @return ProgressBar
*/
public function createProgressBar($max = 0)
{
return new ProgressBar($this->output, $max);
}
/**
* {@inheritdoc}
*/
public function write($messages, $newline = false, $type = self::OUTPUT_NORMAL)
{
$this->output->write($messages, $newline, $type);
}
/**
* {@inheritdoc}
*/
public function writeln($messages, $type = self::OUTPUT_NORMAL)
{
$this->output->writeln($messages, $type);
}
/**
* {@inheritdoc}
*/
public function setVerbosity($level)
{
$this->output->setVerbosity($level);
}
/**
* {@inheritdoc}
*/
public function getVerbosity()
{
return $this->output->getVerbosity();
}
/**
* {@inheritdoc}
*/
public function setDecorated($decorated)
{
$this->output->setDecorated($decorated);
}
/**
* {@inheritdoc}
*/
public function isDecorated()
{
return $this->output->isDecorated();
}
/**
* {@inheritdoc}
*/
public function setFormatter(OutputFormatterInterface $formatter)
{
$this->output->setFormatter($formatter);
}
/**
* {@inheritdoc}
*/
public function getFormatter()
{
return $this->output->getFormatter();
}
/**
* {@inheritdoc}
*/
public function isQuiet()
{
return $this->output->isQuiet();
}
/**
* {@inheritdoc}
*/
public function isVerbose()
{
return $this->output->isVerbose();
}
/**
* {@inheritdoc}
*/
public function isVeryVerbose()
{
return $this->output->isVeryVerbose();
}
/**
* {@inheritdoc}
*/
public function isDebug()
{
return $this->output->isDebug();
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Style;
/**
* Output style helpers.
*
* @author Kevin Bond <kevinbond@gmail.com>
*/
interface StyleInterface
{
/**
* Formats a command title.
*
* @param string $message
*/
public function title($message);
/**
* Formats a section title.
*
* @param string $message
*/
public function section($message);
/**
* Formats a list.
*
* @param array $elements
*/
public function listing(array $elements);
/**
* Formats informational text.
*
* @param string|array $message
*/
public function text($message);
/**
* Formats a success result bar.
*
* @param string|array $message
*/
public function success($message);
/**
* Formats an error result bar.
*
* @param string|array $message
*/
public function error($message);
/**
* Formats an warning result bar.
*
* @param string|array $message
*/
public function warning($message);
/**
* Formats a note admonition.
*
* @param string|array $message
*/
public function note($message);
/**
* Formats a caution admonition.
*
* @param string|array $message
*/
public function caution($message);
/**
* Formats a table.
*
* @param array $headers
* @param array $rows
*/
public function table(array $headers, array $rows);
/**
* Asks a question.
*
* @param string $question
* @param string|null $default
* @param callable|null $validator
*
* @return string
*/
public function ask($question, $default = null, $validator = null);
/**
* Asks a question with the user input hidden.
*
* @param string $question
* @param callable|null $validator
*
* @return string
*/
public function askHidden($question, $validator = null);
/**
* Asks for confirmation.
*
* @param string $question
* @param bool $default
*
* @return bool
*/
public function confirm($question, $default = true);
/**
* Asks a choice question.
*
* @param string $question
* @param array $choices
* @param string|int|null $default
*
* @return string
*/
public function choice($question, array $choices, $default = null);
/**
* Add newline(s).
*
* @param int $count The number of newlines
*/
public function newLine($count = 1);
/**
* Starts the progress output.
*
* @param int $max Maximum steps (0 if unknown)
*/
public function progressStart($max = 0);
/**
* Advances the progress output X steps.
*
* @param int $step Number of steps to advance
*/
public function progressAdvance($step = 1);
/**
* Finishes the progress output.
*/
public function progressFinish();
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Style;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Helper\Helper;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Helper\SymfonyQuestionHelper;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\BufferedOutput;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ChoiceQuestion;
use Symfony\Component\Console\Question\ConfirmationQuestion;
use Symfony\Component\Console\Question\Question;
/**
* Output decorator helpers for the Symfony Style Guide.
*
* @author Kevin Bond <kevinbond@gmail.com>
*/
class SymfonyStyle extends OutputStyle
{
const MAX_LINE_LENGTH = 120;
private $input;
private $questionHelper;
private $progressBar;
private $lineLength;
private $bufferedOutput;
/**
* @param InputInterface $input
* @param OutputInterface $output
*/
public function __construct(InputInterface $input, OutputInterface $output)
{
$this->input = $input;
$this->bufferedOutput = new BufferedOutput($output->getVerbosity(), false, clone $output->getFormatter());
// Windows cmd wraps lines as soon as the terminal width is reached, whether there are following chars or not.
$this->lineLength = min($this->getTerminalWidth() - (int) (DIRECTORY_SEPARATOR === '\\'), self::MAX_LINE_LENGTH);
parent::__construct($output);
}
/**
* Formats a message as a block of text.
*
* @param string|array $messages The message to write in the block
* @param string|null $type The block type (added in [] on first line)
* @param string|null $style The style to apply to the whole block
* @param string $prefix The prefix for the block
* @param bool $padding Whether to add vertical padding
*/
public function block($messages, $type = null, $style = null, $prefix = ' ', $padding = false)
{
$messages = is_array($messages) ? array_values($messages) : array($messages);
$this->autoPrependBlock();
$this->writeln($this->createBlock($messages, $type, $style, $prefix, $padding, true));
$this->newLine();
}
/**
* {@inheritdoc}
*/
public function title($message)
{
$this->autoPrependBlock();
$this->writeln(array(
sprintf('<comment>%s</>', $message),
sprintf('<comment>%s</>', str_repeat('=', Helper::strlenWithoutDecoration($this->getFormatter(), $message))),
));
$this->newLine();
}
/**
* {@inheritdoc}
*/
public function section($message)
{
$this->autoPrependBlock();
$this->writeln(array(
sprintf('<comment>%s</>', $message),
sprintf('<comment>%s</>', str_repeat('-', Helper::strlenWithoutDecoration($this->getFormatter(), $message))),
));
$this->newLine();
}
/**
* {@inheritdoc}
*/
public function listing(array $elements)
{
$this->autoPrependText();
$elements = array_map(function ($element) {
return sprintf(' * %s', $element);
}, $elements);
$this->writeln($elements);
$this->newLine();
}
/**
* {@inheritdoc}
*/
public function text($message)
{
$this->autoPrependText();
$messages = is_array($message) ? array_values($message) : array($message);
foreach ($messages as $message) {
$this->writeln(sprintf(' %s', $message));
}
}
/**
* Formats a command comment.
*
* @param string|array $message
*/
public function comment($message)
{
$messages = is_array($message) ? array_values($message) : array($message);
$this->autoPrependBlock();
$this->writeln($this->createBlock($messages, null, null, '<fg=default;bg=default> // </>'));
$this->newLine();
}
/**
* {@inheritdoc}
*/
public function success($message)
{
$this->block($message, 'OK', 'fg=black;bg=green', ' ', true);
}
/**
* {@inheritdoc}
*/
public function error($message)
{
$this->block($message, 'ERROR', 'fg=white;bg=red', ' ', true);
}
/**
* {@inheritdoc}
*/
public function warning($message)
{
$this->block($message, 'WARNING', 'fg=white;bg=red', ' ', true);
}
/**
* {@inheritdoc}
*/
public function note($message)
{
$this->block($message, 'NOTE', 'fg=yellow', ' ! ');
}
/**
* {@inheritdoc}
*/
public function caution($message)
{
$this->block($message, 'CAUTION', 'fg=white;bg=red', ' ! ', true);
}
/**
* {@inheritdoc}
*/
public function table(array $headers, array $rows)
{
$style = clone Table::getStyleDefinition('symfony-style-guide');
$style->setCellHeaderFormat('<info>%s</info>');
$table = new Table($this);
$table->setHeaders($headers);
$table->setRows($rows);
$table->setStyle($style);
$table->render();
$this->newLine();
}
/**
* {@inheritdoc}
*/
public function ask($question, $default = null, $validator = null)
{
$question = new Question($question, $default);
$question->setValidator($validator);
return $this->askQuestion($question);
}
/**
* {@inheritdoc}
*/
public function askHidden($question, $validator = null)
{
$question = new Question($question);
$question->setHidden(true);
$question->setValidator($validator);
return $this->askQuestion($question);
}
/**
* {@inheritdoc}
*/
public function confirm($question, $default = true)
{
return $this->askQuestion(new ConfirmationQuestion($question, $default));
}
/**
* {@inheritdoc}
*/
public function choice($question, array $choices, $default = null)
{
if (null !== $default) {
$values = array_flip($choices);
$default = $values[$default];
}
return $this->askQuestion(new ChoiceQuestion($question, $choices, $default));
}
/**
* {@inheritdoc}
*/
public function progressStart($max = 0)
{
$this->progressBar = $this->createProgressBar($max);
$this->progressBar->start();
}
/**
* {@inheritdoc}
*/
public function progressAdvance($step = 1)
{
$this->getProgressBar()->advance($step);
}
/**
* {@inheritdoc}
*/
public function progressFinish()
{
$this->getProgressBar()->finish();
$this->newLine(2);
$this->progressBar = null;
}
/**
* {@inheritdoc}
*/
public function createProgressBar($max = 0)
{
$progressBar = parent::createProgressBar($max);
if ('\\' !== DIRECTORY_SEPARATOR) {
$progressBar->setEmptyBarCharacter('â–‘'); // light shade character \u2591
$progressBar->setProgressCharacter('');
$progressBar->setBarCharacter('â–“'); // dark shade character \u2593
}
return $progressBar;
}
/**
* @param Question $question
*
* @return string
*/
public function askQuestion(Question $question)
{
if ($this->input->isInteractive()) {
$this->autoPrependBlock();
}
if (!$this->questionHelper) {
$this->questionHelper = new SymfonyQuestionHelper();
}
$answer = $this->questionHelper->ask($this->input, $this, $question);
if ($this->input->isInteractive()) {
$this->newLine();
$this->bufferedOutput->write("\n");
}
return $answer;
}
/**
* {@inheritdoc}
*/
public function writeln($messages, $type = self::OUTPUT_NORMAL)
{
parent::writeln($messages, $type);
$this->bufferedOutput->writeln($this->reduceBuffer($messages), $type);
}
/**
* {@inheritdoc}
*/
public function write($messages, $newline = false, $type = self::OUTPUT_NORMAL)
{
parent::write($messages, $newline, $type);
$this->bufferedOutput->write($this->reduceBuffer($messages), $newline, $type);
}
/**
* {@inheritdoc}
*/
public function newLine($count = 1)
{
parent::newLine($count);
$this->bufferedOutput->write(str_repeat("\n", $count));
}
/**
* @return ProgressBar
*/
private function getProgressBar()
{
if (!$this->progressBar) {
throw new RuntimeException('The ProgressBar is not started.');
}
return $this->progressBar;
}
private function getTerminalWidth()
{
$application = new Application();
$dimensions = $application->getTerminalDimensions();
return $dimensions[0] ?: self::MAX_LINE_LENGTH;
}
private function autoPrependBlock()
{
$chars = substr(str_replace(PHP_EOL, "\n", $this->bufferedOutput->fetch()), -2);
if (!isset($chars[0])) {
return $this->newLine(); //empty history, so we should start with a new line.
}
//Prepend new line for each non LF chars (This means no blank line was output before)
$this->newLine(2 - substr_count($chars, "\n"));
}
private function autoPrependText()
{
$fetched = $this->bufferedOutput->fetch();
//Prepend new line if last char isn't EOL:
if ("\n" !== substr($fetched, -1)) {
$this->newLine();
}
}
private function reduceBuffer($messages)
{
// We need to know if the two last chars are PHP_EOL
// Preserve the last 4 chars inserted (PHP_EOL on windows is two chars) in the history buffer
return array_map(function ($value) {
return substr($value, -4);
}, array_merge(array($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 = array();
if (null !== $type) {
$type = sprintf('[%s] ', $type);
$indentLength = strlen($type);
$lineIndentation = str_repeat(' ', $indentLength);
}
// wrap and add newlines for each element
foreach ($messages as $key => $message) {
if ($escape) {
$message = OutputFormatter::escape($message);
}
$lines = array_merge($lines, explode(PHP_EOL, wordwrap($message, $this->lineLength - $prefixLength - $indentLength, PHP_EOL, true)));
if (count($messages) > 1 && $key < count($messages) - 1) {
$lines[] = '';
}
}
$firstLineIndex = 0;
if ($padding && $this->isDecorated()) {
$firstLineIndex = 1;
array_unshift($lines, '');
$lines[] = '';
}
foreach ($lines as $i => &$line) {
if (null !== $type) {
$line = $firstLineIndex === $i ? $type.$line : $lineIndentation.$line;
}
$line = $prefix.$line;
$line .= str_repeat(' ', $this->lineLength - Helper::strlenWithoutDecoration($this->getFormatter(), $line));
if ($style) {
$line = sprintf('<%s>%s</>', $style, $line);
}
}
return $lines;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Debug;
use Psr\Log\AbstractLogger;
/**
* A buffering logger that stacks logs for later.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
class BufferingLogger extends AbstractLogger
{
private $logs = array();
public function log($level, $message, array $context = array())
{
$this->logs[] = array($level, $message, $context);
}
public function cleanLogs()
{
$logs = $this->logs;
$this->logs = array();
return $logs;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Debug;
/**
* Registers all the debug tools.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class Debug
{
private static $enabled = false;
/**
* Enables the debug tools.
*
* This method registers an error handler and an exception handler.
*
* If the Symfony ClassLoader component is available, a special
* class loader is also registered.
*
* @param int $errorReportingLevel The level of error reporting you want
* @param bool $displayErrors Whether to display errors (for development) or just log them (for production)
*/
public static function enable($errorReportingLevel = E_ALL, $displayErrors = true)
{
if (static::$enabled) {
return;
}
static::$enabled = true;
if (null !== $errorReportingLevel) {
error_reporting($errorReportingLevel);
} else {
error_reporting(E_ALL);
}
if ('cli' !== PHP_SAPI) {
ini_set('display_errors', 0);
ExceptionHandler::register();
} elseif ($displayErrors && (!ini_get('log_errors') || ini_get('error_log'))) {
// CLI - display errors only if they're not already logged to STDERR
ini_set('display_errors', 1);
}
if ($displayErrors) {
ErrorHandler::register(new ErrorHandler(new BufferingLogger()));
} else {
ErrorHandler::register()->throwAt(0, true);
}
DebugClassLoader::enable();
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Debug;
/**
* Autoloader checking if the class is really defined in the file found.
*
* The ClassLoader will wrap all registered autoloaders
* and will throw an exception if a file is found but does
* not declare the class.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Christophe Coevoet <stof@notk.org>
* @author Nicolas Grekas <p@tchwork.com>
*/
class DebugClassLoader
{
private $classLoader;
private $isFinder;
private static $caseCheck;
private static $deprecated = array();
private static $php7Reserved = array('int', 'float', 'bool', 'string', 'true', 'false', 'null');
private static $darwinCache = array('/' => array('/', array()));
/**
* Constructor.
*
* @param callable $classLoader A class loader
*/
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) {
// filesystem is case sensitive
self::$caseCheck = 0;
} elseif (substr($test, -strlen($file)) === $file) {
// filesystem is case insensitive and realpath() normalizes the case of characters
self::$caseCheck = 1;
} elseif (false !== stripos(PHP_OS, 'darwin')) {
// on MacOSX, HFS+ is case insensitive but realpath() doesn't normalize the case of characters
self::$caseCheck = 2;
} else {
// filesystem case checks failed, fallback to disabling them
self::$caseCheck = 0;
}
}
}
/**
* Gets the wrapped class loader.
*
* @return callable The wrapped class loader
*/
public function getClassLoader()
{
return $this->classLoader;
}
/**
* Wraps all autoloaders.
*/
public static function enable()
{
// Ensures we don't hit https://bugs.php.net/42098
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 = array(new static($function), 'loadClass');
}
spl_autoload_register($function);
}
}
/**
* Disables the wrapping.
*/
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);
}
}
/**
* Loads the given class or interface.
*
* @param string $class The name of the class
*
* @return bool|null True, if loaded
*
* @throws \RuntimeException
*/
public function loadClass($class)
{
ErrorHandler::stackErrors();
try {
if ($this->isFinder) {
if ($file = $this->classLoader[0]->findFile($class)) {
require_once $file;
}
} else {
call_user_func($this->classLoader, $class);
$file = false;
}
} finally {
ErrorHandler::unstackErrors();
}
$exists = class_exists($class, false) || interface_exists($class, false) || trait_exists($class, false);
if ('\\' === $class[0]) {
$class = substr($class, 1);
}
if ($exists) {
$refl = new \ReflectionClass($class);
$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));
}
if (in_array(strtolower($refl->getShortName()), self::$php7Reserved)) {
@trigger_error(sprintf('%s uses a reserved class name (%s) that will break on PHP 7 and higher', $name, $refl->getShortName()), E_USER_DEPRECATED);
} elseif (preg_match('#\n \* @deprecated (.*?)\r?\n \*(?: @|/$)#s', $refl->getDocComment(), $notice)) {
self::$deprecated[$name] = preg_replace('#\s*\r?\n \* +#', ' ', $notice[1]);
} else {
if (2 > $len = 1 + (strpos($name, '\\', 1 + strpos($name, '\\')) ?: strpos($name, '_'))) {
$len = 0;
$ns = '';
} else {
switch ($ns = substr($name, 0, $len)) {
case 'Symfony\Bridge\\':
case 'Symfony\Bundle\\':
case 'Symfony\Component\\':
$ns = 'Symfony\\';
$len = strlen($ns);
break;
}
}
$parent = get_parent_class($class);
if (!$parent || strncmp($ns, $parent, $len)) {
if ($parent && isset(self::$deprecated[$parent]) && strncmp($ns, $parent, $len)) {
@trigger_error(sprintf('The %s class extends %s that is deprecated %s', $name, $parent, self::$deprecated[$parent]), E_USER_DEPRECATED);
}
$parentInterfaces = array();
$deprecatedInterfaces = array();
if ($parent) {
foreach (class_implements($parent) as $interface) {
$parentInterfaces[$interface] = 1;
}
}
foreach ($refl->getInterfaceNames() as $interface) {
if (isset(self::$deprecated[$interface]) && strncmp($ns, $interface, $len)) {
$deprecatedInterfaces[] = $interface;
}
foreach (class_implements($interface) as $interface) {
$parentInterfaces[$interface] = 1;
}
}
foreach ($deprecatedInterfaces as $interface) {
if (!isset($parentInterfaces[$interface])) {
@trigger_error(sprintf('The %s %s %s that is deprecated %s', $name, $refl->isInterface() ? 'interface extends' : 'class implements', $interface, self::$deprecated[$interface]), E_USER_DEPRECATED);
}
}
}
}
}
if ($file) {
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) {
$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 (self::$caseCheck && $tail) {
$tail = DIRECTORY_SEPARATOR.implode(DIRECTORY_SEPARATOR, $tail);
$tailLen = strlen($tail);
$real = $refl->getFileName();
if (2 === self::$caseCheck) {
// realpath() on MacOSX doesn't normalize the case of characters
$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] = array($dir, array());
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])) {
$kFile = $file;
} else {
$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;
}
}
$real .= $dirFiles[$kFile];
}
if (0 === substr_compare($real, $tail, -$tailLen, $tailLen, true)
&& 0 !== substr_compare($real, $tail, -$tailLen, $tailLen, false)
) {
throw new \RuntimeException(sprintf('Case mismatch between class and real file names: %s vs %s in %s', substr($tail, -$tailLen + 1), substr($real, -$tailLen + 1), substr($real, 0, -$tailLen + 1)));
}
}
return true;
}
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Debug;
use Psr\Log\LogLevel;
use Psr\Log\LoggerInterface;
use Symfony\Component\Debug\Exception\ContextErrorException;
use Symfony\Component\Debug\Exception\FatalErrorException;
use Symfony\Component\Debug\Exception\FatalThrowableError;
use Symfony\Component\Debug\Exception\OutOfMemoryException;
use Symfony\Component\Debug\FatalErrorHandler\UndefinedFunctionFatalErrorHandler;
use Symfony\Component\Debug\FatalErrorHandler\UndefinedMethodFatalErrorHandler;
use Symfony\Component\Debug\FatalErrorHandler\ClassNotFoundFatalErrorHandler;
use Symfony\Component\Debug\FatalErrorHandler\FatalErrorHandlerInterface;
/**
* A generic ErrorHandler for the PHP engine.
*
* Provides five bit fields that control how errors are handled:
* - thrownErrors: errors thrown as \ErrorException
* - loggedErrors: logged errors, when not @-silenced
* - scopedErrors: errors thrown or logged with their local context
* - tracedErrors: errors logged with their stack trace, only once for repeated errors
* - screamedErrors: never @-silenced errors
*
* Each error level can be logged by a dedicated PSR-3 logger object.
* Screaming only applies to logging.
* Throwing takes precedence over logging.
* Uncaught exceptions are logged as E_ERROR.
* E_DEPRECATED and E_USER_DEPRECATED levels never throw.
* E_RECOVERABLE_ERROR and E_USER_ERROR levels always throw.
* Non catchable errors that can be detected at shutdown time are logged when the scream bit field allows so.
* As errors have a performance cost, repeated errors are all logged, so that the developer
* can see them and weight them as more important to fix than others of the same level.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
class ErrorHandler
{
private $levels = array(
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 = array(
E_DEPRECATED => array(null, LogLevel::INFO),
E_USER_DEPRECATED => array(null, LogLevel::INFO),
E_NOTICE => array(null, LogLevel::WARNING),
E_USER_NOTICE => array(null, LogLevel::WARNING),
E_STRICT => array(null, LogLevel::WARNING),
E_WARNING => array(null, LogLevel::WARNING),
E_USER_WARNING => array(null, LogLevel::WARNING),
E_COMPILE_WARNING => array(null, LogLevel::WARNING),
E_CORE_WARNING => array(null, LogLevel::WARNING),
E_USER_ERROR => array(null, LogLevel::CRITICAL),
E_RECOVERABLE_ERROR => array(null, LogLevel::CRITICAL),
E_COMPILE_ERROR => array(null, LogLevel::CRITICAL),
E_PARSE => array(null, LogLevel::CRITICAL),
E_ERROR => array(null, LogLevel::CRITICAL),
E_CORE_ERROR => array(null, LogLevel::CRITICAL),
);
private $thrownErrors = 0x1FFF; // E_ALL - E_DEPRECATED - E_USER_DEPRECATED
private $scopedErrors = 0x1FFF; // E_ALL - E_DEPRECATED - E_USER_DEPRECATED
private $tracedErrors = 0x77FB; // E_ALL - E_STRICT - E_PARSE
private $screamedErrors = 0x55; // E_ERROR + E_CORE_ERROR + E_COMPILE_ERROR + E_PARSE
private $loggedErrors = 0;
private $loggedTraces = array();
private $isRecursive = 0;
private $isRoot = false;
private $exceptionHandler;
private $bootstrappingLogger;
private static $reservedMemory;
private static $stackedErrors = array();
private static $stackedErrorLevels = array();
private static $toStringException = null;
/**
* Registers the error handler.
*
* @param self|null $handler The handler to register
* @param bool $replace Whether to replace or not any existing handler
*
* @return self The registered error handler
*/
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(array($handler, 'handleError'))) {
restore_error_handler();
// Specifying the error types earlier would expose us to https://bugs.php.net/63206
set_error_handler(array($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) {
$handler->setExceptionHandler(set_exception_handler(array($handler, 'handleException')));
} else {
restore_error_handler();
}
$handler->throwAt(E_ALL & $handler->thrownErrors, true);
return $handler;
}
public function __construct(BufferingLogger $bootstrappingLogger = null)
{
if ($bootstrappingLogger) {
$this->bootstrappingLogger = $bootstrappingLogger;
$this->setDefaultLogger($bootstrappingLogger);
}
}
/**
* Sets a logger to non assigned errors levels.
*
* @param LoggerInterface $logger A PSR-3 logger to put as default for the given levels
* @param array|int $levels An array map of E_* to LogLevel::* or an integer bit field of E_* constants
* @param bool $replace Whether to replace or not any existing logger
*/
public function setDefaultLogger(LoggerInterface $logger, $levels = E_ALL, $replace = false)
{
$loggers = array();
if (is_array($levels)) {
foreach ($levels as $type => $logLevel) {
if (empty($this->loggers[$type][0]) || $replace || $this->loggers[$type][0] === $this->bootstrappingLogger) {
$loggers[$type] = array($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);
}
/**
* Sets a logger for each error level.
*
* @param array $loggers Error levels to [LoggerInterface|null, LogLevel::*] map
*
* @return array The previous map
*
* @throws \InvalidArgumentException
*/
public function setLoggers(array $loggers)
{
$prevLogged = $this->loggedErrors;
$prev = $this->loggers;
$flush = array();
foreach ($loggers as $type => $log) {
if (!isset($prev[$type])) {
throw new \InvalidArgumentException('Unknown error type: '.$type);
}
if (!is_array($log)) {
$log = array($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]['type'];
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;
}
/**
* Sets a user exception handler.
*
* @param callable $handler A handler that will be called on Exception
*
* @return callable|null The previous exception handler
*/
public function setExceptionHandler(callable $handler = null)
{
$prev = $this->exceptionHandler;
$this->exceptionHandler = $handler;
return $prev;
}
/**
* Sets the PHP error levels that throw an exception when a PHP error occurs.
*
* @param int $levels A bit field of E_* constants for thrown errors
* @param bool $replace Replace or amend the previous value
*
* @return int The previous value
*/
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;
}
/**
* Sets the PHP error levels for which local variables are preserved.
*
* @param int $levels A bit field of E_* constants for scoped errors
* @param bool $replace Replace or amend the previous value
*
* @return int The previous value
*/
public function scopeAt($levels, $replace = false)
{
$prev = $this->scopedErrors;
$this->scopedErrors = (int) $levels;
if (!$replace) {
$this->scopedErrors |= $prev;
}
return $prev;
}
/**
* Sets the PHP error levels for which the stack trace is preserved.
*
* @param int $levels A bit field of E_* constants for traced errors
* @param bool $replace Replace or amend the previous value
*
* @return int The previous value
*/
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;
}
/**
* Re-registers as a PHP error handler if levels changed.
*/
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(array($this, 'handleError'), $this->thrownErrors | $this->loggedErrors);
} else {
set_error_handler(array($this, 'handleError'));
}
}
}
}
/**
* Handles errors by filtering then logging them according to the configured bit fields.
*
* @param int $type One of the E_* constants
* @param string $message
* @param string $file
* @param int $line
* @param array $context
* @param array $backtrace
*
* @return bool Returns false when no handling happens so that the PHP engine can handle the error itself
*
* @throws \ErrorException When $this->thrownErrors requests so
*
* @internal
*/
public function handleError($type, $message, $file, $line, array $context, array $backtrace = null)
{
$level = error_reporting() | 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 $type && $log;
}
if (null !== $backtrace && $type & E_ERROR) {
// E_ERROR fatal errors are triggered on HHVM when
// hhvm.error_handling.call_user_handler_on_fatals=1
// which is the way to get their backtrace.
$this->handleFatalError(compact('type', 'message', 'file', 'line', 'backtrace'));
return true;
}
if ($throw) {
if (null !== self::$toStringException) {
$throw = self::$toStringException;
self::$toStringException = null;
} elseif (($this->scopedErrors & $type) && class_exists(ContextErrorException::class)) {
$throw = new ContextErrorException($this->levels[$type].': '.$message, 0, $type, $file, $line, $context);
} else {
$throw = new \ErrorException($this->levels[$type].': '.$message, 0, $type, $file, $line);
}
if (E_USER_ERROR & $type) {
$backtrace = $backtrace ?: $throw->getTrace();
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'])
) {
// Here, we know trigger_error() has been called from __toString().
// HHVM is fine with throwing from __toString() but PHP triggers a fatal error instead.
// A small convention allows working around the limitation:
// given a caught $e exception in __toString(), quitting the method with
// `return trigger_error($e, E_USER_ERROR);` allows this error handler
// to make $e get through the __toString() barrier.
foreach ($context as $e) {
if (($e instanceof \Exception || $e instanceof \Throwable) && $e->__toString() === $message) {
if (1 === $i) {
// On HHVM
$throw = $e;
break;
}
self::$toStringException = $e;
return true;
}
}
if (1 < $i) {
// On PHP (not on HHVM), display the original error message instead of the default one.
$this->handleException($throw);
// Stop the process by giving back the error to the native handler.
return false;
}
}
}
}
throw $throw;
}
// For duplicated errors, log the trace only once
$e = md5("{$type}/{$line}/{$file}\x00{$message}", true);
$trace = true;
if (!($this->tracedErrors & $type) || isset($this->loggedTraces[$e])) {
$trace = false;
} else {
$this->loggedTraces[$e] = 1;
}
$e = compact('type', 'file', 'line', 'level');
if ($type & $level) {
if ($this->scopedErrors & $type) {
$e['scope_vars'] = $context;
if ($trace) {
$e['stack'] = $backtrace ?: debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT);
}
} elseif ($trace) {
if (null === $backtrace) {
$e['stack'] = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
} else {
foreach ($backtrace as &$frame) {
unset($frame['args'], $frame);
}
$e['stack'] = $backtrace;
}
}
}
if ($this->isRecursive) {
$log = 0;
} elseif (self::$stackedErrorLevels) {
self::$stackedErrors[] = array($this->loggers[$type][0], ($type & $level) ? $this->loggers[$type][1] : LogLevel::DEBUG, $message, $e);
} else {
try {
$this->isRecursive = true;
$this->loggers[$type][0]->log(($type & $level) ? $this->loggers[$type][1] : LogLevel::DEBUG, $message, $e);
} finally {
$this->isRecursive = false;
}
}
return $type && $log;
}
/**
* Handles an exception by logging then forwarding it to another handler.
*
* @param \Exception|\Throwable $exception An exception to handle
* @param array $error An array as returned by error_get_last()
*
* @internal
*/
public function handleException($exception, array $error = null)
{
if (!$exception instanceof \Exception) {
$exception = new FatalThrowableError($exception);
}
$type = $exception instanceof FatalErrorException ? $exception->getSeverity() : E_ERROR;
if (($this->loggedErrors & $type) || $exception instanceof FatalThrowableError) {
$e = array(
'type' => $type,
'file' => $exception->getFile(),
'line' => $exception->getLine(),
'level' => error_reporting(),
'stack' => $exception->getTrace(),
);
if ($exception instanceof FatalErrorException) {
if ($exception instanceof FatalThrowableError) {
$error = array(
'type' => $type,
'message' => $message = $exception->getMessage(),
'file' => $e['file'],
'line' => $e['line'],
);
} else {
$message = 'Fatal '.$exception->getMessage();
}
} elseif ($exception instanceof \ErrorException) {
$message = 'Uncaught '.$exception->getMessage();
if ($exception instanceof ContextErrorException) {
$e['context'] = $exception->getContext();
}
} else {
$message = 'Uncaught Exception: '.$exception->getMessage();
}
}
if ($this->loggedErrors & $type) {
$this->loggers[$type][0]->log($this->loggers[$type][1], $message, $e);
}
if ($exception instanceof FatalErrorException && !$exception instanceof OutOfMemoryException && $error) {
foreach ($this->getFatalErrorHandlers() as $handler) {
if ($e = $handler->handleError($error, $exception)) {
$exception = $e;
break;
}
}
}
if (empty($this->exceptionHandler)) {
throw $exception; // Give back $exception to the native handler
}
try {
call_user_func($this->exceptionHandler, $exception);
} catch (\Exception $handlerException) {
} catch (\Throwable $handlerException) {
}
if (isset($handlerException)) {
$this->exceptionHandler = null;
$this->handleException($handlerException);
}
}
/**
* Shutdown registered function for handling PHP fatal errors.
*
* @param array $error An array as returned by error_get_last()
*
* @internal
*/
public static function handleFatalError(array $error = null)
{
if (null === self::$reservedMemory) {
return;
}
self::$reservedMemory = null;
$handler = set_error_handler('var_dump');
$handler = is_array($handler) ? $handler[0] : null;
restore_error_handler();
if (!$handler instanceof self) {
return;
}
if (null === $error) {
$error = error_get_last();
}
try {
while (self::$stackedErrorLevels) {
static::unstackErrors();
}
} catch (\Exception $exception) {
// Handled below
} catch (\Throwable $exception) {
// Handled below
}
if ($error && $error['type'] &= E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR) {
// Let's not throw anymore but keep logging
$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);
}
} elseif (!isset($exception)) {
return;
}
try {
$handler->handleException($exception, $error);
} catch (FatalErrorException $e) {
// Ignore this re-throw
}
}
/**
* Configures the error handler for delayed handling.
* Ensures also that non-catchable fatal errors are never silenced.
*
* As shown by http://bugs.php.net/42098 and http://bugs.php.net/60724
* PHP has a compile stage where it behaves unusually. To workaround it,
* we plug an error handler that only stacks errors for later.
*
* The most important feature of this is to prevent
* autoloading until unstackErrors() is called.
*/
public static function stackErrors()
{
self::$stackedErrorLevels[] = error_reporting(error_reporting() | E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR);
}
/**
* Unstacks stacked errors and forwards to the logger.
*/
public static function unstackErrors()
{
$level = array_pop(self::$stackedErrorLevels);
if (null !== $level) {
$e = error_reporting($level);
if ($e !== ($level | E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR)) {
// If the user changed the error level, do not overwrite it
error_reporting($e);
}
}
if (empty(self::$stackedErrorLevels)) {
$errors = self::$stackedErrors;
self::$stackedErrors = array();
foreach ($errors as $e) {
$e[0]->log($e[1], $e[2], $e[3]);
}
}
}
/**
* Gets the fatal error handlers.
*
* Override this method if you want to define more fatal error handlers.
*
* @return FatalErrorHandlerInterface[] An array of FatalErrorHandlerInterface
*/
protected function getFatalErrorHandlers()
{
return array(
new UndefinedFunctionFatalErrorHandler(),
new UndefinedMethodFatalErrorHandler(),
new ClassNotFoundFatalErrorHandler(),
);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Debug\Exception;
/**
* Class (or Trait or Interface) Not Found Exception.
*
* @author Konstanton Myakshin <koc-dp@yandex.ru>
*/
class ClassNotFoundException extends FatalErrorException
{
public function __construct($message, \ErrorException $previous)
{
parent::__construct(
$message,
$previous->getCode(),
$previous->getSeverity(),
$previous->getFile(),
$previous->getLine(),
$previous->getPrevious()
);
$this->setTrace($previous->getTrace());
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Debug\Exception;
/**
* Error Exception with Variable Context.
*
* @author Christian Sciberras <uuf6429@gmail.com>
*/
class ContextErrorException extends \ErrorException
{
private $context = array();
public function __construct($message, $code, $severity, $filename, $lineno, $context = array())
{
parent::__construct($message, $code, $severity, $filename, $lineno);
$this->context = $context;
}
/**
* @return array Array of variables that existed when the exception occurred
*/
public function getContext()
{
return $this->context;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Debug\Exception;
/**
* Fatal Error Exception.
*
* @author Konstanton Myakshin <koc-dp@yandex.ru>
*/
class FatalErrorException extends \ErrorException
{
public function __construct($message, $code, $severity, $filename, $lineno, $traceOffset = null, $traceArgs = true, array $trace = null)
{
parent::__construct($message, $code, $severity, $filename, $lineno);
if (null !== $trace) {
if (!$traceArgs) {
foreach ($trace as &$frame) {
unset($frame['args'], $frame['this'], $frame);
}
}
$this->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'])) {
// XDebug pre 2.1.1 doesn't currently set the call type key http://bugs.xdebug.org/view.php?id=695
if (isset($frame['class'])) {
$frame['type'] = '::';
}
} elseif ('dynamic' === $frame['type']) {
$frame['type'] = '->';
} elseif ('static' === $frame['type']) {
$frame['type'] = '::';
}
// XDebug also has a different name for the parameters array
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 = array();
}
$this->setTrace($trace);
}
}
protected function setTrace($trace)
{
$traceReflector = new \ReflectionProperty('Exception', 'trace');
$traceReflector->setAccessible(true);
$traceReflector->setValue($this, $trace);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Debug\Exception;
/**
* Fatal Throwable Error.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
class FatalThrowableError extends FatalErrorException
{
public function __construct(\Throwable $e)
{
if ($e instanceof \ParseError) {
$message = 'Parse error: '.$e->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()
);
$this->setTrace($e->getTrace());
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Debug\Exception;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
/**
* FlattenException wraps a PHP Exception to be able to serialize it.
*
* Basically, this class removes all objects from the trace.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class FlattenException
{
private $message;
private $code;
private $previous;
private $trace;
private $class;
private $statusCode;
private $headers;
private $file;
private $line;
public static function create(\Exception $exception, $statusCode = null, array $headers = array())
{
$e = new static();
$e->setMessage($exception->getMessage());
$e->setCode($exception->getCode());
if ($exception instanceof HttpExceptionInterface) {
$statusCode = $exception->getStatusCode();
$headers = array_merge($headers, $exception->getHeaders());
}
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 = array();
foreach (array_merge(array($this), $this->getAllPrevious()) as $exception) {
$exceptions[] = array(
'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(FlattenException $previous)
{
$this->previous = $previous;
}
public function getAllPrevious()
{
$exceptions = array();
$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 = array();
$this->trace[] = array(
'namespace' => '',
'short_class' => '',
'class' => '',
'type' => '',
'function' => '',
'file' => $file,
'line' => $line,
'args' => array(),
);
foreach ($trace as $entry) {
$class = '';
$namespace = '';
if (isset($entry['class'])) {
$parts = explode('\\', $entry['class']);
$class = array_pop($parts);
$namespace = implode('\\', $parts);
}
$this->trace[] = array(
'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']) : array(),
);
}
}
private function flattenArgs($args, $level = 0, &$count = 0)
{
$result = array();
foreach ($args as $key => $value) {
if (++$count > 1e4) {
return array('array', '*SKIPPED over 10000 entries*');
}
if ($value instanceof \__PHP_Incomplete_Class) {
// is_object() returns false on PHP<=7.1
$result[$key] = array('incomplete-object', $this->getClassNameFromIncomplete($value));
} elseif (is_object($value)) {
$result[$key] = array('object', get_class($value));
} elseif (is_array($value)) {
if ($level > 10) {
$result[$key] = array('array', '*DEEP NESTED ARRAY*');
} else {
$result[$key] = array('array', $this->flattenArgs($value, $level + 1, $count));
}
} elseif (null === $value) {
$result[$key] = array('null', null);
} elseif (is_bool($value)) {
$result[$key] = array('boolean', $value);
} elseif (is_resource($value)) {
$result[$key] = array('resource', get_resource_type($value));
} else {
$result[$key] = array('string', (string) $value);
}
}
return $result;
}
private function getClassNameFromIncomplete(\__PHP_Incomplete_Class $value)
{
$array = new \ArrayObject($value);
return $array['__PHP_Incomplete_Class_Name'];
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Debug\Exception;
/**
* Out of memory exception.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
class OutOfMemoryException extends FatalErrorException
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Debug\Exception;
/**
* Undefined Function Exception.
*
* @author Konstanton Myakshin <koc-dp@yandex.ru>
*/
class UndefinedFunctionException extends FatalErrorException
{
public function __construct($message, \ErrorException $previous)
{
parent::__construct(
$message,
$previous->getCode(),
$previous->getSeverity(),
$previous->getFile(),
$previous->getLine(),
$previous->getPrevious()
);
$this->setTrace($previous->getTrace());
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Debug\Exception;
/**
* Undefined Method Exception.
*
* @author Grégoire Pineau <lyrixx@lyrixx.info>
*/
class UndefinedMethodException extends FatalErrorException
{
public function __construct($message, \ErrorException $previous)
{
parent::__construct(
$message,
$previous->getCode(),
$previous->getSeverity(),
$previous->getFile(),
$previous->getLine(),
$previous->getPrevious()
);
$this->setTrace($previous->getTrace());
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Debug;
use Symfony\Component\Debug\Exception\FlattenException;
use Symfony\Component\Debug\Exception\OutOfMemoryException;
/**
* ExceptionHandler converts an exception to a Response object.
*
* It is mostly useful in debug mode to replace the default PHP/XDebug
* output with something prettier and more useful.
*
* As this class is mainly used during Kernel boot, where nothing is yet
* available, the Response content is always HTML.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Nicolas Grekas <p@tchwork.com>
*/
class ExceptionHandler
{
private $debug;
private $charset;
private $handler;
private $caughtBuffer;
private $caughtLength;
private $fileLinkFormat;
public function __construct($debug = true, $charset = null, $fileLinkFormat = null)
{
$this->debug = $debug;
$this->charset = $charset ?: ini_get('default_charset') ?: 'UTF-8';
$this->fileLinkFormat = $fileLinkFormat ?: ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format');
}
/**
* Registers the exception handler.
*
* @param bool $debug Enable/disable debug mode, where the stack trace is displayed
* @param string|null $charset The charset used by exception messages
* @param string|null $fileLinkFormat The IDE link template
*
* @return ExceptionHandler The registered exception handler
*/
public static function register($debug = true, $charset = null, $fileLinkFormat = null)
{
$handler = new static($debug, $charset, $fileLinkFormat);
$prev = set_exception_handler(array($handler, 'handle'));
if (is_array($prev) && $prev[0] instanceof ErrorHandler) {
restore_exception_handler();
$prev[0]->setExceptionHandler(array($handler, 'handle'));
}
return $handler;
}
/**
* Sets a user exception handler.
*
* @param callable $handler An handler that will be called on Exception
*
* @return callable|null The previous exception handler if any
*/
public function setHandler(callable $handler = null)
{
$old = $this->handler;
$this->handler = $handler;
return $old;
}
/**
* Sets the format for links to source files.
*
* @param string $format The format for links to source files
*
* @return string The previous file link format
*/
public function setFileLinkFormat($format)
{
$old = $this->fileLinkFormat;
$this->fileLinkFormat = $format;
return $old;
}
/**
* Sends a response for the given Exception.
*
* To be as fail-safe as possible, the exception is first handled
* by our simple exception handler, then by the user exception handler.
* The latter takes precedence and any output from the former is cancelled,
* if and only if nothing bad happens in this handling path.
*/
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()) {
// Empty loop, everything is in the condition
}
if (isset($this->caughtBuffer[0])) {
ob_start(function ($buffer) {
if ($this->caughtLength) {
// use substr_replace() instead of substr() for mbstring overloading resistance
$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) {
// All handlers failed. Let PHP handle that now.
throw $exception;
}
}
}
/**
* Sends the error associated with the given Exception as a plain PHP response.
*
* This method uses plain PHP functions like header() and echo to output
* the response.
*
* @param \Exception|FlattenException $exception An \Exception or FlattenException instance
*/
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));
}
/**
* Gets the full HTML content associated with the given exception.
*
* @param \Exception|FlattenException $exception An \Exception or FlattenException instance
*
* @return string The HTML content as a string
*/
public function getHtml($exception)
{
if (!$exception instanceof FlattenException) {
$exception = FlattenException::create($exception);
}
return $this->decorate($this->getContent($exception), $this->getStylesheet($exception));
}
/**
* Gets the HTML content associated with the given exception.
*
* @param FlattenException $exception A FlattenException instance
*
* @return string The content as a string
*/
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.';
}
$content = '';
if ($this->debug) {
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'
<h2 class="block_exception clear_fix">
<span class="exception_counter">%d/%d</span>
<span class="exception_title">%s%s:</span>
<span class="exception_message">%s</span>
</h2>
<div class="block">
<ol class="traces list_exception">
EOF
, $ind, $total, $class, $this->formatPath($e['trace'][0]['file'], $e['trace'][0]['line']), $message);
foreach ($e['trace'] as $trace) {
$content .= ' <li>';
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 .= "</li>\n";
}
$content .= " </ol>\n</div>\n";
}
} catch (\Exception $e) {
// something nasty happened and we cannot throw an exception anymore
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.';
}
}
}
return <<<EOF
<div id="sf-resetcontent" class="sf-reset">
<h1>$title</h1>
$content
</div>
EOF;
}
/**
* Gets the stylesheet associated with the given exception.
*
* @param FlattenException $exception A FlattenException instance
*
* @return string The stylesheet as a string
*/
public function getStylesheet(FlattenException $exception)
{
return <<<'EOF'
.sf-reset { font: 11px Verdana, Arial, sans-serif; color: #333 }
.sf-reset .clear { clear:both; height:0; font-size:0; line-height:0; }
.sf-reset .clear_fix:after { display:block; height:0; clear:both; visibility:hidden; }
.sf-reset .clear_fix { display:inline-block; }
.sf-reset * html .clear_fix { height:1%; }
.sf-reset .clear_fix { display:block; }
.sf-reset, .sf-reset .block { margin: auto }
.sf-reset abbr { border-bottom: 1px dotted #000; cursor: help; }
.sf-reset p { font-size:14px; line-height:20px; color:#868686; padding-bottom:20px }
.sf-reset strong { font-weight:bold; }
.sf-reset a { color:#6c6159; cursor: default; }
.sf-reset a img { border:none; }
.sf-reset a:hover { text-decoration:underline; }
.sf-reset em { font-style:italic; }
.sf-reset h1, .sf-reset h2 { font: 20px Georgia, "Times New Roman", Times, serif }
.sf-reset .exception_counter { background-color: #fff; color: #333; padding: 6px; float: left; margin-right: 10px; float: left; display: block; }
.sf-reset .exception_title { margin-left: 3em; margin-bottom: 0.7em; display: block; }
.sf-reset .exception_message { margin-left: 3em; display: block; }
.sf-reset .traces li { font-size:12px; padding: 2px 4px; list-style-type:decimal; margin-left:20px; }
.sf-reset .block { background-color:#FFFFFF; padding:10px 28px; margin-bottom:20px;
-webkit-border-bottom-right-radius: 16px;
-webkit-border-bottom-left-radius: 16px;
-moz-border-radius-bottomright: 16px;
-moz-border-radius-bottomleft: 16px;
border-bottom-right-radius: 16px;
border-bottom-left-radius: 16px;
border-bottom:1px solid #ccc;
border-right:1px solid #ccc;
border-left:1px solid #ccc;
word-wrap: break-word;
}
.sf-reset .block_exception { background-color:#ddd; color: #333; padding:20px;
-webkit-border-top-left-radius: 16px;
-webkit-border-top-right-radius: 16px;
-moz-border-radius-topleft: 16px;
-moz-border-radius-topright: 16px;
border-top-left-radius: 16px;
border-top-right-radius: 16px;
border-top:1px solid #ccc;
border-right:1px solid #ccc;
border-left:1px solid #ccc;
overflow: hidden;
word-wrap: break-word;
}
.sf-reset a { background:none; color:#868686; text-decoration:none; }
.sf-reset a:hover { background:none; color:#313131; text-decoration:underline; }
.sf-reset ol { padding: 10px 0; }
.sf-reset h1 { background-color:#FFFFFF; padding: 15px 28px; margin-bottom: 20px;
-webkit-border-radius: 10px;
-moz-border-radius: 10px;
border-radius: 10px;
border: 1px solid #ccc;
}
EOF;
}
private function decorate($content, $css)
{
return <<<EOF
<!DOCTYPE html>
<html>
<head>
<meta charset="{$this->charset}" />
<meta name="robots" content="noindex,nofollow" />
<style>
/* Copyright (c) 2010, Yahoo! Inc. All rights reserved. Code licensed under the BSD License: http://developer.yahoo.com/yui/license.html */
html{color:#000;background:#FFF;}body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,textarea,p,blockquote,th,td{margin:0;padding:0;}table{border-collapse:collapse;border-spacing:0;}fieldset,img{border:0;}address,caption,cite,code,dfn,em,strong,th,var{font-style:normal;font-weight:normal;}li{list-style:none;}caption,th{text-align:left;}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal;}q:before,q:after{content:'';}abbr,acronym{border:0;font-variant:normal;}sup{vertical-align:text-top;}sub{vertical-align:text-bottom;}input,textarea,select{font-family:inherit;font-size:inherit;font-weight:inherit;}input,textarea,select{*font-size:100%;}legend{color:#000;}
html { background: #eee; padding: 10px }
img { border: 0; }
#sf-resetcontent { width:970px; margin:0 auto; }
$css
</style>
</head>
<body>
$content
</body>
</html>
EOF;
}
private function formatClass($class)
{
$parts = explode('\\', $class);
return sprintf('<abbr title="%s">%s</abbr>', $class, array_pop($parts));
}
private function formatPath($path, $line)
{
$path = $this->escapeHtml($path);
$file = preg_match('#[^/\\\\]*$#', $path, $file) ? $file[0] : $path;
if ($linkFormat = $this->fileLinkFormat) {
$link = strtr($this->escapeHtml($linkFormat), array('%f' => $path, '%l' => (int) $line));
return sprintf(' in <a href="%s" title="Go to source">%s line %d</a>', $link, $file, $line);
}
return sprintf(' in <a title="%s line %3$d" ondblclick="var f=this.innerHTML;this.innerHTML=this.title;this.title=f;">%s line %d</a>', $path, $file, $line);
}
/**
* Formats an array as a string.
*
* @param array $args The argument array
*
* @return string
*/
private function formatArgs(array $args)
{
$result = array();
foreach ($args as $key => $item) {
if ('object' === $item[0]) {
$formattedValue = sprintf('<em>object</em>(%s)', $this->formatClass($item[1]));
} elseif ('array' === $item[0]) {
$formattedValue = sprintf('<em>array</em>(%s)', is_array($item[1]) ? $this->formatArgs($item[1]) : $item[1]);
} elseif ('string' === $item[0]) {
$formattedValue = sprintf("'%s'", $this->escapeHtml($item[1]));
} elseif ('null' === $item[0]) {
$formattedValue = '<em>null</em>';
} elseif ('boolean' === $item[0]) {
$formattedValue = '<em>'.strtolower(var_export($item[1], true)).'</em>';
} elseif ('resource' === $item[0]) {
$formattedValue = '<em>resource</em>';
} else {
$formattedValue = str_replace("\n", '', var_export($this->escapeHtml((string) $item[1]), true));
}
$result[] = is_int($key) ? $formattedValue : sprintf("'%s' => %s", $key, $formattedValue);
}
return implode(', ', $result);
}
/**
* HTML-encodes a string.
*/
private function escapeHtml($str)
{
return htmlspecialchars($str, ENT_QUOTES | ENT_SUBSTITUTE, $this->charset);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Debug\FatalErrorHandler;
use Symfony\Component\Debug\Exception\ClassNotFoundException;
use Symfony\Component\Debug\Exception\FatalErrorException;
use Symfony\Component\Debug\DebugClassLoader;
use Composer\Autoload\ClassLoader as ComposerClassLoader;
use Symfony\Component\ClassLoader\ClassLoader as SymfonyClassLoader;
/**
* ErrorHandler for classes that do not exist.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class ClassNotFoundFatalErrorHandler implements FatalErrorHandlerInterface
{
/**
* {@inheritdoc}
*/
public function handleError(array $error, FatalErrorException $exception)
{
$messageLen = strlen($error['message']);
$notFoundSuffix = '\' not found';
$notFoundSuffixLen = strlen($notFoundSuffix);
if ($notFoundSuffixLen > $messageLen) {
return;
}
if (0 !== substr_compare($error['message'], $notFoundSuffix, -$notFoundSuffixLen)) {
return;
}
foreach (array('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);
}
}
/**
* Tries to guess the full namespace for a given class name.
*
* By default, it looks for PSR-0 and PSR-4 classes registered via a Symfony or a Composer
* autoloader (that should cover all common cases).
*
* @param string $class A class name (without its namespace)
*
* @return array An array of possible fully qualified class names
*/
private function getClassCandidates($class)
{
if (!is_array($functions = spl_autoload_functions())) {
return array();
}
// find Symfony and Composer autoloaders
$classes = array();
foreach ($functions as $function) {
if (!is_array($function)) {
continue;
}
// get class loaders wrapped by DebugClassLoader
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);
}
/**
* @param string $path
* @param string $class
* @param string $prefix
*
* @return array
*/
private function findClassInPath($path, $class, $prefix)
{
if (!$path = realpath($path.'/'.strtr($prefix, '\\_', '//')) ?: realpath($path.'/'.dirname(strtr($prefix, '\\_', '//'))) ?: realpath($path)) {
return array();
}
$classes = array();
$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;
}
/**
* @param string $path
* @param string $file
* @param string $prefix
*
* @return string|null
*/
private function convertFileToClass($path, $file, $prefix)
{
$candidates = array(
// namespaced class
$namespacedClass = str_replace(array($path.DIRECTORY_SEPARATOR, '.php', '/'), array('', '', '\\'), $file),
// namespaced class (with target dir)
$prefix.$namespacedClass,
// namespaced class (with target dir and separator)
$prefix.'\\'.$namespacedClass,
// PEAR class
str_replace('\\', '_', $namespacedClass),
// PEAR class (with target dir)
str_replace('\\', '_', $prefix.$namespacedClass),
// PEAR class (with target dir and separator)
str_replace('\\', '_', $prefix.'\\'.$namespacedClass),
);
if ($prefix) {
$candidates = array_filter($candidates, function ($candidate) use ($prefix) {return 0 === strpos($candidate, $prefix);});
}
// We cannot use the autoloader here as most of them use require; but if the class
// is not found, the new autoloader call will require the file again leading to a
// "cannot redeclare class" error.
foreach ($candidates as $candidate) {
if ($this->classExists($candidate)) {
return $candidate;
}
}
require_once $file;
foreach ($candidates as $candidate) {
if ($this->classExists($candidate)) {
return $candidate;
}
}
}
/**
* @param string $class
*
* @return bool
*/
private function classExists($class)
{
return class_exists($class, false) || interface_exists($class, false) || trait_exists($class, false);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Debug\FatalErrorHandler;
use Symfony\Component\Debug\Exception\FatalErrorException;
/**
* Attempts to convert fatal errors to exceptions.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
interface FatalErrorHandlerInterface
{
/**
* Attempts to convert an error into an exception.
*
* @param array $error An array as returned by error_get_last()
* @param FatalErrorException $exception A FatalErrorException instance
*
* @return FatalErrorException|null A FatalErrorException instance if the class is able to convert the error, null otherwise
*/
public function handleError(array $error, FatalErrorException $exception);
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Debug\FatalErrorHandler;
use Symfony\Component\Debug\Exception\UndefinedFunctionException;
use Symfony\Component\Debug\Exception\FatalErrorException;
/**
* ErrorHandler for undefined functions.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class UndefinedFunctionFatalErrorHandler implements FatalErrorHandlerInterface
{
/**
* {@inheritdoc}
*/
public function handleError(array $error, FatalErrorException $exception)
{
$messageLen = strlen($error['message']);
$notFoundSuffix = '()';
$notFoundSuffixLen = strlen($notFoundSuffix);
if ($notFoundSuffixLen > $messageLen) {
return;
}
if (0 !== substr_compare($error['message'], $notFoundSuffix, -$notFoundSuffixLen)) {
return;
}
$prefix = 'Call to undefined function ';
$prefixLen = strlen($prefix);
if (0 !== strpos($error['message'], $prefix)) {
return;
}
$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 = array();
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);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Debug\FatalErrorHandler;
use Symfony\Component\Debug\Exception\FatalErrorException;
use Symfony\Component\Debug\Exception\UndefinedMethodException;
/**
* ErrorHandler for undefined methods.
*
* @author Grégoire Pineau <lyrixx@lyrixx.info>
*/
class UndefinedMethodFatalErrorHandler implements FatalErrorHandlerInterface
{
/**
* {@inheritdoc}
*/
public function handleError(array $error, FatalErrorException $exception)
{
preg_match('/^Call to undefined method (.*)::(.*)\(\)$/', $error['message'], $matches);
if (!$matches) {
return;
}
$className = $matches[1];
$methodName = $matches[2];
$message = sprintf('Attempted to call an undefined method named "%s" of class "%s".', $methodName, $className);
$candidates = array();
foreach (get_class_methods($className) as $definedMethodName) {
$lev = levenshtein($methodName, $definedMethodName);
if ($lev <= strlen($methodName) / 3 || false !== strpos($definedMethodName, $methodName)) {
$candidates[] = $definedMethodName;
}
}
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 UndefinedMethodException($message, $exception);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\EventDispatcher;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Lazily loads listeners and subscribers from the dependency injection
* container.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Bernhard Schussek <bschussek@gmail.com>
* @author Jordan Alliot <jordan.alliot@gmail.com>
*/
class ContainerAwareEventDispatcher extends EventDispatcher
{
/**
* The container from where services are loaded.
*
* @var ContainerInterface
*/
private $container;
/**
* The service IDs of the event listeners and subscribers.
*
* @var array
*/
private $listenerIds = array();
/**
* The services registered as listeners.
*
* @var array
*/
private $listeners = array();
/**
* Constructor.
*
* @param ContainerInterface $container A ContainerInterface instance
*/
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
/**
* Adds a service as event listener.
*
* @param string $eventName Event for which the listener is added
* @param array $callback The service ID of the listener service & the method
* name that has to be called
* @param int $priority The higher this value, the earlier an event listener
* will be triggered in the chain.
* Defaults to 0.
*
* @throws \InvalidArgumentException
*/
public function addListenerService($eventName, $callback, $priority = 0)
{
if (!is_array($callback) || 2 !== count($callback)) {
throw new \InvalidArgumentException('Expected an array("service", "method") argument');
}
$this->listenerIds[$eventName][] = array($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, $priority)) {
$key = $serviceId.'.'.$method;
if (isset($this->listeners[$eventName][$key]) && $listener === array($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);
}
/**
* {@inheritdoc}
*/
public function hasListeners($eventName = null)
{
if (null === $eventName) {
return (bool) count($this->listenerIds) || (bool) count($this->listeners);
}
if (isset($this->listenerIds[$eventName])) {
return true;
}
return parent::hasListeners($eventName);
}
/**
* {@inheritdoc}
*/
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);
}
/**
* {@inheritdoc}
*/
public function getListenerPriority($eventName, $listener)
{
$this->lazyLoad($eventName);
return parent::getListenerPriority($eventName, $listener);
}
/**
* Adds a service as event subscriber.
*
* @param string $serviceId The service ID of the subscriber service
* @param string $class The service's class name (which must implement EventSubscriberInterface)
*/
public function addSubscriberService($serviceId, $class)
{
foreach ($class::getSubscribedEvents() as $eventName => $params) {
if (is_string($params)) {
$this->listenerIds[$eventName][] = array($serviceId, $params, 0);
} elseif (is_string($params[0])) {
$this->listenerIds[$eventName][] = array($serviceId, $params[0], isset($params[1]) ? $params[1] : 0);
} else {
foreach ($params as $listener) {
$this->listenerIds[$eventName][] = array($serviceId, $listener[0], isset($listener[1]) ? $listener[1] : 0);
}
}
}
}
public function getContainer()
{
return $this->container;
}
/**
* Lazily loads listeners for this event from the dependency injection
* container.
*
* @param string $eventName The name of the event to dispatch. The name of
* the event is the name of the method that is
* invoked on listeners.
*/
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, array($listener, $method), $priority);
} elseif ($listener !== $this->listeners[$eventName][$key]) {
parent::removeListener($eventName, array($this->listeners[$eventName][$key], $method));
$this->addListener($eventName, array($listener, $method), $priority);
}
$this->listeners[$eventName][$key] = $listener;
}
}
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\EventDispatcher\Debug;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\EventDispatcher\Event;
use Symfony\Component\Stopwatch\Stopwatch;
use Psr\Log\LoggerInterface;
/**
* Collects some data about event listeners.
*
* This event dispatcher delegates the dispatching to another one.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class TraceableEventDispatcher implements TraceableEventDispatcherInterface
{
protected $logger;
protected $stopwatch;
private $called;
private $dispatcher;
private $wrappedListeners;
/**
* Constructor.
*
* @param EventDispatcherInterface $dispatcher An EventDispatcherInterface instance
* @param Stopwatch $stopwatch A Stopwatch instance
* @param LoggerInterface $logger A LoggerInterface instance
*/
public function __construct(EventDispatcherInterface $dispatcher, Stopwatch $stopwatch, LoggerInterface $logger = null)
{
$this->dispatcher = $dispatcher;
$this->stopwatch = $stopwatch;
$this->logger = $logger;
$this->called = array();
$this->wrappedListeners = array();
}
/**
* {@inheritdoc}
*/
public function addListener($eventName, $listener, $priority = 0)
{
$this->dispatcher->addListener($eventName, $listener, $priority);
}
/**
* {@inheritdoc}
*/
public function addSubscriber(EventSubscriberInterface $subscriber)
{
$this->dispatcher->addSubscriber($subscriber);
}
/**
* {@inheritdoc}
*/
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);
}
/**
* {@inheritdoc}
*/
public function removeSubscriber(EventSubscriberInterface $subscriber)
{
return $this->dispatcher->removeSubscriber($subscriber);
}
/**
* {@inheritdoc}
*/
public function getListeners($eventName = null)
{
return $this->dispatcher->getListeners($eventName);
}
/**
* {@inheritdoc}
*/
public function getListenerPriority($eventName, $listener)
{
return $this->dispatcher->getListenerPriority($eventName, $listener);
}
/**
* {@inheritdoc}
*/
public function hasListeners($eventName = null)
{
return $this->dispatcher->hasListeners($eventName);
}
/**
* {@inheritdoc}
*/
public function dispatch($eventName, Event $event = null)
{
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);
$this->preDispatch($eventName, $event);
$e = $this->stopwatch->start($eventName, 'section');
$this->dispatcher->dispatch($eventName, $event);
if ($e->isStarted()) {
$e->stop();
}
$this->postDispatch($eventName, $event);
$this->postProcess($eventName);
return $event;
}
/**
* {@inheritdoc}
*/
public function getCalledListeners()
{
$called = array();
foreach ($this->called as $eventName => $listeners) {
foreach ($listeners as $listener) {
$info = $this->getListenerInfo($listener->getWrappedListener(), $eventName);
$called[$eventName.'.'.$info['pretty']] = $info;
}
}
return $called;
}
/**
* {@inheritdoc}
*/
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.', array('exception' => $e));
}
// unable to retrieve the uncalled listeners
return array();
}
$notCalled = array();
foreach ($allListeners as $eventName => $listeners) {
foreach ($listeners as $listener) {
$called = false;
if (isset($this->called[$eventName])) {
foreach ($this->called[$eventName] as $l) {
if ($l->getWrappedListener() === $listener) {
$called = true;
break;
}
}
}
if (!$called) {
$info = $this->getListenerInfo($listener, $eventName);
$notCalled[$eventName.'.'.$info['pretty']] = $info;
}
}
}
uasort($notCalled, array($this, 'sortListenersByPriority'));
return $notCalled;
}
/**
* Proxies all method calls to the original event dispatcher.
*
* @param string $method The method name
* @param array $arguments The method arguments
*
* @return mixed
*/
public function __call($method, $arguments)
{
return call_user_func_array(array($this->dispatcher, $method), $arguments);
}
/**
* Called before dispatching the event.
*
* @param string $eventName The event name
* @param Event $event The event
*/
protected function preDispatch($eventName, Event $event)
{
}
/**
* Called after dispatching the event.
*
* @param string $eventName The event name
* @param Event $event The event
*/
protected function postDispatch($eventName, Event $event)
{
}
private function preProcess($eventName)
{
foreach ($this->dispatcher->getListeners($eventName) as $listener) {
$info = $this->getListenerInfo($listener, $eventName);
$name = isset($info['class']) ? $info['class'] : $info['type'];
$wrappedListener = new WrappedListener($listener, $name, $this->stopwatch, $this);
$this->wrappedListeners[$eventName][] = $wrappedListener;
$this->dispatcher->removeListener($eventName, $listener);
$this->dispatcher->addListener($eventName, $wrappedListener, $info['priority']);
}
}
private function postProcess($eventName)
{
unset($this->wrappedListeners[$eventName]);
$skipped = false;
foreach ($this->dispatcher->getListeners($eventName) as $listener) {
if (!$listener instanceof WrappedListener) { // #12845: a new listener was added during dispatch.
continue;
}
// Unwrap listener
$priority = $this->getListenerPriority($eventName, $listener);
$this->dispatcher->removeListener($eventName, $listener);
$this->dispatcher->addListener($eventName, $listener->getWrappedListener(), $priority);
$info = $this->getListenerInfo($listener->getWrappedListener(), $eventName);
if ($listener->wasCalled()) {
if (null !== $this->logger) {
$this->logger->debug('Notified event "{event}" to listener "{listener}".', array('event' => $eventName, 'listener' => $info['pretty']));
}
if (!isset($this->called[$eventName])) {
$this->called[$eventName] = new \SplObjectStorage();
}
$this->called[$eventName]->attach($listener);
}
if (null !== $this->logger && $skipped) {
$this->logger->debug('Listener "{listener}" was not called for event "{event}".', array('listener' => $info['pretty'], 'event' => $eventName));
}
if ($listener->stoppedPropagation()) {
if (null !== $this->logger) {
$this->logger->debug('Listener "{listener}" stopped propagation of the event "{event}".', array('listener' => $info['pretty'], 'event' => $eventName));
}
$skipped = true;
}
}
}
/**
* Returns information about the listener.
*
* @param object $listener The listener
* @param string $eventName The event name
*
* @return array Information about the listener
*/
private function getListenerInfo($listener, $eventName)
{
$info = array(
'event' => $eventName,
'priority' => $this->getListenerPriority($eventName, $listener),
);
if ($listener instanceof \Closure) {
$info += array(
'type' => 'Closure',
'pretty' => 'closure',
);
} elseif (is_string($listener)) {
try {
$r = new \ReflectionFunction($listener);
$file = $r->getFileName();
$line = $r->getStartLine();
} catch (\ReflectionException $e) {
$file = null;
$line = null;
}
$info += array(
'type' => 'Function',
'function' => $listener,
'file' => $file,
'line' => $line,
'pretty' => $listener,
);
} elseif (is_array($listener) || (is_object($listener) && is_callable($listener))) {
if (!is_array($listener)) {
$listener = array($listener, '__invoke');
}
$class = is_object($listener[0]) ? get_class($listener[0]) : $listener[0];
try {
$r = new \ReflectionMethod($class, $listener[1]);
$file = $r->getFileName();
$line = $r->getStartLine();
} catch (\ReflectionException $e) {
$file = null;
$line = null;
}
$info += array(
'type' => 'Method',
'class' => $class,
'method' => $listener[1],
'file' => $file,
'line' => $line,
'pretty' => $class.'::'.$listener[1],
);
}
return $info;
}
private function sortListenersByPriority($a, $b)
{
if (is_int($a['priority']) && !is_int($b['priority'])) {
return 1;
}
if (!is_int($a['priority']) && is_int($b['priority'])) {
return -1;
}
if ($a['priority'] === $b['priority']) {
return 0;
}
if ($a['priority'] > $b['priority']) {
return -1;
}
return 1;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\EventDispatcher\Debug;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
/**
* @author Fabien Potencier <fabien@symfony.com>
*/
interface TraceableEventDispatcherInterface extends EventDispatcherInterface
{
/**
* Gets the called listeners.
*
* @return array An array of called listeners
*/
public function getCalledListeners();
/**
* Gets the not called listeners.
*
* @return array An array of not called listeners
*/
public function getNotCalledListeners();
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\EventDispatcher\Debug;
use Symfony\Component\Stopwatch\Stopwatch;
use Symfony\Component\EventDispatcher\Event;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
/**
* @author Fabien Potencier <fabien@symfony.com>
*/
class WrappedListener
{
private $listener;
private $name;
private $called;
private $stoppedPropagation;
private $stopwatch;
private $dispatcher;
public function __construct($listener, $name, Stopwatch $stopwatch, EventDispatcherInterface $dispatcher = null)
{
$this->listener = $listener;
$this->name = $name;
$this->stopwatch = $stopwatch;
$this->dispatcher = $dispatcher;
$this->called = false;
$this->stoppedPropagation = false;
}
public function getWrappedListener()
{
return $this->listener;
}
public function wasCalled()
{
return $this->called;
}
public function stoppedPropagation()
{
return $this->stoppedPropagation;
}
public function __invoke(Event $event, $eventName, EventDispatcherInterface $dispatcher)
{
$this->called = true;
$e = $this->stopwatch->start($this->name, 'event_listener');
call_user_func($this->listener, $event, $eventName, $this->dispatcher ?: $dispatcher);
if ($e->isStarted()) {
$e->stop();
}
if ($event->isPropagationStopped()) {
$this->stoppedPropagation = true;
}
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\EventDispatcher\DependencyInjection;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
/**
* Compiler pass to register tagged services for an event dispatcher.
*/
class RegisterListenersPass implements CompilerPassInterface
{
/**
* @var string
*/
protected $dispatcherService;
/**
* @var string
*/
protected $listenerTag;
/**
* @var string
*/
protected $subscriberTag;
/**
* Constructor.
*
* @param string $dispatcherService Service name of the event dispatcher in processed container
* @param string $listenerTag Tag name used for listener
* @param string $subscriberTag Tag name used for subscribers
*/
public function __construct($dispatcherService = 'event_dispatcher', $listenerTag = 'kernel.event_listener', $subscriberTag = 'kernel.event_subscriber')
{
$this->dispatcherService = $dispatcherService;
$this->listenerTag = $listenerTag;
$this->subscriberTag = $subscriberTag;
}
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) as $id => $events) {
$def = $container->getDefinition($id);
if (!$def->isPublic()) {
throw new \InvalidArgumentException(sprintf('The service "%s" must be public as event listeners are lazy-loaded.', $id));
}
if ($def->isAbstract()) {
throw new \InvalidArgumentException(sprintf('The service "%s" must not be abstract as event listeners are lazy-loaded.', $id));
}
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(array(
'/(?<=\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('addListenerService', array($event['event'], array($id, $event['method']), $priority));
}
}
foreach ($container->findTaggedServiceIds($this->subscriberTag) as $id => $attributes) {
$def = $container->getDefinition($id);
if (!$def->isPublic()) {
throw new \InvalidArgumentException(sprintf('The service "%s" must be public as event subscribers are lazy-loaded.', $id));
}
if ($def->isAbstract()) {
throw new \InvalidArgumentException(sprintf('The service "%s" must not be abstract as event subscribers are lazy-loaded.', $id));
}
// We must assume that the class value has been correctly filled, even if the service is created by a factory
$class = $container->getParameterBag()->resolveValue($def->getClass());
$interface = 'Symfony\Component\EventDispatcher\EventSubscriberInterface';
if (!is_subclass_of($class, $interface)) {
if (!class_exists($class, false)) {
throw new \InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id));
}
throw new \InvalidArgumentException(sprintf('Service "%s" must implement interface "%s".', $id, $interface));
}
$definition->addMethodCall('addSubscriberService', array($id, $class));
}
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\EventDispatcher;
/**
* Event is the base class for classes containing event data.
*
* This class contains no event data. It is used by events that do not pass
* state information to an event handler when an event is raised.
*
* You can call the method stopPropagation() to abort the execution of
* further listeners in your event listener.
*
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class Event
{
/**
* @var bool Whether no further event listeners should be triggered
*/
private $propagationStopped = false;
/**
* Returns whether further event listeners should be triggered.
*
* @see Event::stopPropagation()
*
* @return bool Whether propagation was already stopped for this event
*/
public function isPropagationStopped()
{
return $this->propagationStopped;
}
/**
* Stops the propagation of the event to further event listeners.
*
* If multiple event listeners are connected to the same event, no
* further event listener will be triggered once any trigger calls
* stopPropagation().
*/
public function stopPropagation()
{
$this->propagationStopped = true;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\EventDispatcher;
/**
* The EventDispatcherInterface is the central point of Symfony's event listener system.
*
* Listeners are registered on the manager and events are dispatched through the
* manager.
*
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
* @author Bernhard Schussek <bschussek@gmail.com>
* @author Fabien Potencier <fabien@symfony.com>
* @author Jordi Boggiano <j.boggiano@seld.be>
* @author Jordan Alliot <jordan.alliot@gmail.com>
*/
class EventDispatcher implements EventDispatcherInterface
{
private $listeners = array();
private $sorted = array();
/**
* {@inheritdoc}
*/
public function dispatch($eventName, Event $event = null)
{
if (null === $event) {
$event = new Event();
}
if ($listeners = $this->getListeners($eventName)) {
$this->doDispatch($listeners, $eventName, $event);
}
return $event;
}
/**
* {@inheritdoc}
*/
public function getListeners($eventName = null)
{
if (null !== $eventName) {
if (!isset($this->listeners[$eventName])) {
return array();
}
if (!isset($this->sorted[$eventName])) {
$this->sortListeners($eventName);
}
return $this->sorted[$eventName];
}
foreach ($this->listeners as $eventName => $eventListeners) {
if (!isset($this->sorted[$eventName])) {
$this->sortListeners($eventName);
}
}
return array_filter($this->sorted);
}
/**
* {@inheritdoc}
*/
public function getListenerPriority($eventName, $listener)
{
if (!isset($this->listeners[$eventName])) {
return;
}
foreach ($this->listeners[$eventName] as $priority => $listeners) {
if (false !== in_array($listener, $listeners, true)) {
return $priority;
}
}
}
/**
* {@inheritdoc}
*/
public function hasListeners($eventName = null)
{
return (bool) count($this->getListeners($eventName));
}
/**
* {@inheritdoc}
*/
public function addListener($eventName, $listener, $priority = 0)
{
$this->listeners[$eventName][$priority][] = $listener;
unset($this->sorted[$eventName]);
}
/**
* {@inheritdoc}
*/
public function removeListener($eventName, $listener)
{
if (!isset($this->listeners[$eventName])) {
return;
}
foreach ($this->listeners[$eventName] as $priority => $listeners) {
if (false !== ($key = array_search($listener, $listeners, true))) {
unset($this->listeners[$eventName][$priority][$key], $this->sorted[$eventName]);
}
}
}
/**
* {@inheritdoc}
*/
public function addSubscriber(EventSubscriberInterface $subscriber)
{
foreach ($subscriber->getSubscribedEvents() as $eventName => $params) {
if (is_string($params)) {
$this->addListener($eventName, array($subscriber, $params));
} elseif (is_string($params[0])) {
$this->addListener($eventName, array($subscriber, $params[0]), isset($params[1]) ? $params[1] : 0);
} else {
foreach ($params as $listener) {
$this->addListener($eventName, array($subscriber, $listener[0]), isset($listener[1]) ? $listener[1] : 0);
}
}
}
}
/**
* {@inheritdoc}
*/
public function removeSubscriber(EventSubscriberInterface $subscriber)
{
foreach ($subscriber->getSubscribedEvents() as $eventName => $params) {
if (is_array($params) && is_array($params[0])) {
foreach ($params as $listener) {
$this->removeListener($eventName, array($subscriber, $listener[0]));
}
} else {
$this->removeListener($eventName, array($subscriber, is_string($params) ? $params : $params[0]));
}
}
}
/**
* Triggers the listeners of an event.
*
* This method can be overridden to add functionality that is executed
* for each listener.
*
* @param callable[] $listeners The event listeners
* @param string $eventName The name of the event to dispatch
* @param Event $event The event object to pass to the event handlers/listeners
*/
protected function doDispatch($listeners, $eventName, Event $event)
{
foreach ($listeners as $listener) {
if ($event->isPropagationStopped()) {
break;
}
call_user_func($listener, $event, $eventName, $this);
}
}
/**
* Sorts the internal list of listeners for the given event by priority.
*
* @param string $eventName The name of the event
*/
private function sortListeners($eventName)
{
krsort($this->listeners[$eventName]);
$this->sorted[$eventName] = call_user_func_array('array_merge', $this->listeners[$eventName]);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\EventDispatcher;
/**
* The EventDispatcherInterface is the central point of Symfony's event listener system.
* Listeners are registered on the manager and events are dispatched through the
* manager.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
interface EventDispatcherInterface
{
/**
* Dispatches an event to all registered listeners.
*
* @param string $eventName The name of the event to dispatch. The name of
* the event is the name of the method that is
* invoked on listeners.
* @param Event $event The event to pass to the event handlers/listeners
* If not supplied, an empty Event instance is created.
*
* @return Event
*/
public function dispatch($eventName, Event $event = null);
/**
* Adds an event listener that listens on the specified events.
*
* @param string $eventName The event to listen on
* @param callable $listener The listener
* @param int $priority The higher this value, the earlier an event
* listener will be triggered in the chain (defaults to 0)
*/
public function addListener($eventName, $listener, $priority = 0);
/**
* Adds an event subscriber.
*
* The subscriber is asked for all the events he is
* interested in and added as a listener for these events.
*
* @param EventSubscriberInterface $subscriber The subscriber
*/
public function addSubscriber(EventSubscriberInterface $subscriber);
/**
* Removes an event listener from the specified events.
*
* @param string $eventName The event to remove a listener from
* @param callable $listener The listener to remove
*/
public function removeListener($eventName, $listener);
/**
* Removes an event subscriber.
*
* @param EventSubscriberInterface $subscriber The subscriber
*/
public function removeSubscriber(EventSubscriberInterface $subscriber);
/**
* Gets the listeners of a specific event or all listeners sorted by descending priority.
*
* @param string $eventName The name of the event
*
* @return array The event listeners for the specified event, or all event listeners by event name
*/
public function getListeners($eventName = null);
/**
* Gets the listener priority for a specific event.
*
* Returns null if the event or the listener does not exist.
*
* @param string $eventName The name of the event
* @param callable $listener The listener
*
* @return int|null The event listener priority
*/
public function getListenerPriority($eventName, $listener);
/**
* Checks whether an event has any registered listeners.
*
* @param string $eventName The name of the event
*
* @return bool true if the specified event has any listeners, false otherwise
*/
public function hasListeners($eventName = null);
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\EventDispatcher;
/**
* An EventSubscriber knows himself what events he is interested in.
* If an EventSubscriber is added to an EventDispatcherInterface, the manager invokes
* {@link getSubscribedEvents} and registers the subscriber as a listener for all
* returned events.
*
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
* @author Bernhard Schussek <bschussek@gmail.com>
*/
interface EventSubscriberInterface
{
/**
* Returns an array of event names this subscriber wants to listen to.
*
* The array keys are event names and the value can be:
*
* * The method name to call (priority defaults to 0)
* * An array composed of the method name to call and the priority
* * An array of arrays composed of the method names to call and respective
* priorities, or 0 if unset
*
* For instance:
*
* * array('eventName' => 'methodName')
* * array('eventName' => array('methodName', $priority))
* * array('eventName' => array(array('methodName1', $priority), array('methodName2')))
*
* @return array The event names to listen to
*/
public static function getSubscribedEvents();
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\EventDispatcher;
/**
* Event encapsulation class.
*
* Encapsulates events thus decoupling the observer from the subject they encapsulate.
*
* @author Drak <drak@zikula.org>
*/
class GenericEvent extends Event implements \ArrayAccess, \IteratorAggregate
{
/**
* Event subject.
*
* @var mixed usually object or callable
*/
protected $subject;
/**
* Array of arguments.
*
* @var array
*/
protected $arguments;
/**
* Encapsulate an event with $subject and $args.
*
* @param mixed $subject The subject of the event, usually an object
* @param array $arguments Arguments to store in the event
*/
public function __construct($subject = null, array $arguments = array())
{
$this->subject = $subject;
$this->arguments = $arguments;
}
/**
* Getter for subject property.
*
* @return mixed $subject The observer subject
*/
public function getSubject()
{
return $this->subject;
}
/**
* Get argument by key.
*
* @param string $key Key
*
* @return mixed Contents of array key
*
* @throws \InvalidArgumentException If key is not found.
*/
public function getArgument($key)
{
if ($this->hasArgument($key)) {
return $this->arguments[$key];
}
throw new \InvalidArgumentException(sprintf('Argument "%s" not found.', $key));
}
/**
* Add argument to event.
*
* @param string $key Argument name
* @param mixed $value Value
*
* @return GenericEvent
*/
public function setArgument($key, $value)
{
$this->arguments[$key] = $value;
return $this;
}
/**
* Getter for all arguments.
*
* @return array
*/
public function getArguments()
{
return $this->arguments;
}
/**
* Set args property.
*
* @param array $args Arguments
*
* @return GenericEvent
*/
public function setArguments(array $args = array())
{
$this->arguments = $args;
return $this;
}
/**
* Has argument.
*
* @param string $key Key of arguments array
*
* @return bool
*/
public function hasArgument($key)
{
return array_key_exists($key, $this->arguments);
}
/**
* ArrayAccess for argument getter.
*
* @param string $key Array key
*
* @return mixed
*
* @throws \InvalidArgumentException If key does not exist in $this->args.
*/
public function offsetGet($key)
{
return $this->getArgument($key);
}
/**
* ArrayAccess for argument setter.
*
* @param string $key Array key to set
* @param mixed $value Value
*/
public function offsetSet($key, $value)
{
$this->setArgument($key, $value);
}
/**
* ArrayAccess for unset argument.
*
* @param string $key Array key
*/
public function offsetUnset($key)
{
if ($this->hasArgument($key)) {
unset($this->arguments[$key]);
}
}
/**
* ArrayAccess has argument.
*
* @param string $key Array key
*
* @return bool
*/
public function offsetExists($key)
{
return $this->hasArgument($key);
}
/**
* IteratorAggregate for iterating over the object like an array.
*
* @return \ArrayIterator
*/
public function getIterator()
{
return new \ArrayIterator($this->arguments);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\EventDispatcher;
/**
* A read-only proxy for an event dispatcher.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class ImmutableEventDispatcher implements EventDispatcherInterface
{
/**
* The proxied dispatcher.
*
* @var EventDispatcherInterface
*/
private $dispatcher;
/**
* Creates an unmodifiable proxy for an event dispatcher.
*
* @param EventDispatcherInterface $dispatcher The proxied event dispatcher
*/
public function __construct(EventDispatcherInterface $dispatcher)
{
$this->dispatcher = $dispatcher;
}
/**
* {@inheritdoc}
*/
public function dispatch($eventName, Event $event = null)
{
return $this->dispatcher->dispatch($eventName, $event);
}
/**
* {@inheritdoc}
*/
public function addListener($eventName, $listener, $priority = 0)
{
throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.');
}
/**
* {@inheritdoc}
*/
public function addSubscriber(EventSubscriberInterface $subscriber)
{
throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.');
}
/**
* {@inheritdoc}
*/
public function removeListener($eventName, $listener)
{
throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.');
}
/**
* {@inheritdoc}
*/
public function removeSubscriber(EventSubscriberInterface $subscriber)
{
throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.');
}
/**
* {@inheritdoc}
*/
public function getListeners($eventName = null)
{
return $this->dispatcher->getListeners($eventName);
}
/**
* {@inheritdoc}
*/
public function getListenerPriority($eventName, $listener)
{
return $this->dispatcher->getListenerPriority($eventName, $listener);
}
/**
* {@inheritdoc}
*/
public function hasListeners($eventName = null)
{
return $this->dispatcher->hasListeners($eventName);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Filesystem\Exception;
/**
* Exception interface for all exceptions thrown by the component.
*
* @author Romain Neutron <imprec@gmail.com>
*/
interface ExceptionInterface
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Filesystem\Exception;
/**
* Exception class thrown when a file couldn't be found.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Christian Gärtner <christiangaertner.film@googlemail.com>
*/
class FileNotFoundException extends IOException
{
public function __construct($message = null, $code = 0, \Exception $previous = null, $path = null)
{
if (null === $message) {
if (null === $path) {
$message = 'File could not be found.';
} else {
$message = sprintf('File "%s" could not be found.', $path);
}
}
parent::__construct($message, $code, $previous, $path);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Filesystem\Exception;
/**
* Exception class thrown when a filesystem operation failure happens.
*
* @author Romain Neutron <imprec@gmail.com>
* @author Christian Gärtner <christiangaertner.film@googlemail.com>
* @author Fabien Potencier <fabien@symfony.com>
*/
class IOException extends \RuntimeException implements IOExceptionInterface
{
private $path;
public function __construct($message, $code = 0, \Exception $previous = null, $path = null)
{
$this->path = $path;
parent::__construct($message, $code, $previous);
}
/**
* {@inheritdoc}
*/
public function getPath()
{
return $this->path;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Filesystem\Exception;
/**
* IOException interface for file and input/output stream related exceptions thrown by the component.
*
* @author Christian Gärtner <christiangaertner.film@googlemail.com>
*/
interface IOExceptionInterface extends ExceptionInterface
{
/**
* Returns the associated path for the exception.
*
* @return string The path
*/
public function getPath();
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Filesystem;
use Symfony\Component\Filesystem\Exception\IOException;
use Symfony\Component\Filesystem\Exception\FileNotFoundException;
/**
* Provides basic utility to manipulate the file system.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class Filesystem
{
/**
* Copies a file.
*
* If the target file is older than the origin file, it's always overwritten.
* If the target file is newer, it is overwritten only when the
* $overwriteNewerFiles option is set to true.
*
* @param string $originFile The original filename
* @param string $targetFile The target filename
* @param bool $overwriteNewerFiles If true, target files newer than origin files are overwritten
*
* @throws FileNotFoundException When originFile doesn't exist
* @throws IOException When copy fails
*/
public function copy($originFile, $targetFile, $overwriteNewerFiles = false)
{
if (stream_is_local($originFile) && !is_file($originFile)) {
throw new FileNotFoundException(sprintf('Failed to copy "%s" because file does not exist.', $originFile), 0, null, $originFile);
}
$this->mkdir(dirname($targetFile));
$doCopy = true;
if (!$overwriteNewerFiles && null === parse_url($originFile, PHP_URL_HOST) && is_file($targetFile)) {
$doCopy = filemtime($originFile) > filemtime($targetFile);
}
if ($doCopy) {
// https://bugs.php.net/bug.php?id=64634
if (false === $source = @fopen($originFile, 'r')) {
throw new IOException(sprintf('Failed to copy "%s" to "%s" because source file could not be opened for reading.', $originFile, $targetFile), 0, null, $originFile);
}
// Stream context created to allow files overwrite when using FTP stream wrapper - disabled by default
if (false === $target = @fopen($targetFile, 'w', null, stream_context_create(array('ftp' => array('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);
}
// Like `cp`, preserve executable permission bits
@chmod($targetFile, fileperms($targetFile) | (fileperms($originFile) & 0111));
if (stream_is_local($originFile) && $bytesCopied !== ($bytesOrigin = filesize($originFile))) {
throw new IOException(sprintf('Failed to copy the whole content of "%s" to "%s" (%g of %g bytes copied).', $originFile, $targetFile, $bytesCopied, $bytesOrigin), 0, null, $originFile);
}
}
}
/**
* Creates a directory recursively.
*
* @param string|array|\Traversable $dirs The directory path
* @param int $mode The directory mode
*
* @throws IOException On any directory creation failure
*/
public function mkdir($dirs, $mode = 0777)
{
foreach ($this->toIterator($dirs) as $dir) {
if (is_dir($dir)) {
continue;
}
if (true !== @mkdir($dir, $mode, true)) {
$error = error_get_last();
if (!is_dir($dir)) {
// The directory was not created by a concurrent process. Let's throw an exception with a developer friendly error message if we have one
if ($error) {
throw new IOException(sprintf('Failed to create "%s": %s.', $dir, $error['message']), 0, null, $dir);
}
throw new IOException(sprintf('Failed to create "%s"', $dir), 0, null, $dir);
}
}
}
}
/**
* Checks the existence of files or directories.
*
* @param string|array|\Traversable $files A filename, an array of files, or a \Traversable instance to check
*
* @return bool true if the file exists, false otherwise
*/
public function exists($files)
{
foreach ($this->toIterator($files) as $file) {
if ('\\' === DIRECTORY_SEPARATOR && strlen($file) > 258) {
throw new IOException('Could not check if file exist because path length exceeds 258 characters.', 0, null, $file);
}
if (!file_exists($file)) {
return false;
}
}
return true;
}
/**
* Sets access and modification time of file.
*
* @param string|array|\Traversable $files A filename, an array of files, or a \Traversable instance to create
* @param int $time The touch time as a Unix timestamp
* @param int $atime The access time as a Unix timestamp
*
* @throws IOException When touch fails
*/
public function touch($files, $time = null, $atime = null)
{
foreach ($this->toIterator($files) as $file) {
$touch = $time ? @touch($file, $time, $atime) : @touch($file);
if (true !== $touch) {
throw new IOException(sprintf('Failed to touch "%s".', $file), 0, null, $file);
}
}
}
/**
* Removes files or directories.
*
* @param string|array|\Traversable $files A filename, an array of files, or a \Traversable instance to remove
*
* @throws IOException When removal fails
*/
public function remove($files)
{
if ($files instanceof \Traversable) {
$files = iterator_to_array($files, false);
} elseif (!is_array($files)) {
$files = array($files);
}
$files = array_reverse($files);
foreach ($files as $file) {
if (is_link($file)) {
// See https://bugs.php.net/52176
if (!@(unlink($file) || '\\' !== DIRECTORY_SEPARATOR || rmdir($file)) && file_exists($file)) {
$error = error_get_last();
throw new IOException(sprintf('Failed to remove symlink "%s": %s.', $file, $error['message']));
}
} elseif (is_dir($file)) {
$this->remove(new \FilesystemIterator($file, \FilesystemIterator::CURRENT_AS_PATHNAME | \FilesystemIterator::SKIP_DOTS));
if (!@rmdir($file) && file_exists($file)) {
$error = error_get_last();
throw new IOException(sprintf('Failed to remove directory "%s": %s.', $file, $error['message']));
}
} elseif (!@unlink($file) && file_exists($file)) {
$error = error_get_last();
throw new IOException(sprintf('Failed to remove file "%s": %s.', $file, $error['message']));
}
}
}
/**
* Change mode for an array of files or directories.
*
* @param string|array|\Traversable $files A filename, an array of files, or a \Traversable instance to change mode
* @param int $mode The new mode (octal)
* @param int $umask The mode mask (octal)
* @param bool $recursive Whether change the mod recursively or not
*
* @throws IOException When the change fail
*/
public function chmod($files, $mode, $umask = 0000, $recursive = false)
{
foreach ($this->toIterator($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);
}
}
}
/**
* Change the owner of an array of files or directories.
*
* @param string|array|\Traversable $files A filename, an array of files, or a \Traversable instance to change owner
* @param string $user The new owner user name
* @param bool $recursive Whether change the owner recursively or not
*
* @throws IOException When the change fail
*/
public function chown($files, $user, $recursive = false)
{
foreach ($this->toIterator($files) as $file) {
if ($recursive && is_dir($file) && !is_link($file)) {
$this->chown(new \FilesystemIterator($file), $user, true);
}
if (is_link($file) && function_exists('lchown')) {
if (true !== @lchown($file, $user)) {
throw new IOException(sprintf('Failed to chown file "%s".', $file), 0, null, $file);
}
} else {
if (true !== @chown($file, $user)) {
throw new IOException(sprintf('Failed to chown file "%s".', $file), 0, null, $file);
}
}
}
}
/**
* Change the group of an array of files or directories.
*
* @param string|array|\Traversable $files A filename, an array of files, or a \Traversable instance to change group
* @param string $group The group name
* @param bool $recursive Whether change the group recursively or not
*
* @throws IOException When the change fail
*/
public function chgrp($files, $group, $recursive = false)
{
foreach ($this->toIterator($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);
}
}
}
}
/**
* Renames a file or a directory.
*
* @param string $origin The origin filename or directory
* @param string $target The new filename or directory
* @param bool $overwrite Whether to overwrite the target if it already exists
*
* @throws IOException When target file or directory already exists
* @throws IOException When origin cannot be renamed
*/
public function rename($origin, $target, $overwrite = false)
{
// we check that target does not exist
if (!$overwrite && $this->isReadable($target)) {
throw new IOException(sprintf('Cannot rename because the target "%s" already exists.', $target), 0, null, $target);
}
if (true !== @rename($origin, $target)) {
throw new IOException(sprintf('Cannot rename "%s" to "%s".', $origin, $target), 0, null, $target);
}
}
/**
* Tells whether a file exists and is readable.
*
* @param string $filename Path to the file
*
* @return bool
*
* @throws IOException When windows path is longer than 258 characters
*/
private function isReadable($filename)
{
if ('\\' === DIRECTORY_SEPARATOR && strlen($filename) > 258) {
throw new IOException('Could not check if file is readable because path length exceeds 258 characters.', 0, null, $filename);
}
return is_readable($filename);
}
/**
* Creates a symbolic link or copy a directory.
*
* @param string $originDir The origin directory path
* @param string $targetDir The symbolic link name
* @param bool $copyOnWindows Whether to copy files if on Windows
*
* @throws IOException When symlink fails
*/
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));
$ok = false;
if (is_link($targetDir)) {
if (readlink($targetDir) != $originDir) {
$this->remove($targetDir);
} else {
$ok = true;
}
}
if (!$ok && true !== @symlink($originDir, $targetDir)) {
$report = error_get_last();
if (is_array($report)) {
if ('\\' === DIRECTORY_SEPARATOR && false !== strpos($report['message'], 'error code(1314)')) {
throw new IOException('Unable to create symlink due to error code 1314: \'A required privilege is not held by the client\'. Do you have the required Administrator-rights?', 0, null, $targetDir);
}
}
throw new IOException(sprintf('Failed to create symbolic link from "%s" to "%s".', $originDir, $targetDir), 0, null, $targetDir);
}
}
/**
* Given an existing path, convert it to a path relative to a given starting path.
*
* @param string $endPath Absolute path of target
* @param string $startPath Absolute path where traversal begins
*
* @return string Path of target relative to starting path
*/
public function makePathRelative($endPath, $startPath)
{
// Normalize separators on Windows
if ('\\' === DIRECTORY_SEPARATOR) {
$endPath = str_replace('\\', '/', $endPath);
$startPath = str_replace('\\', '/', $startPath);
}
// Split the paths into arrays
$startPathArr = explode('/', trim($startPath, '/'));
$endPathArr = explode('/', trim($endPath, '/'));
// Find for which directory the common path stops
$index = 0;
while (isset($startPathArr[$index]) && isset($endPathArr[$index]) && $startPathArr[$index] === $endPathArr[$index]) {
++$index;
}
// Determine how deep the start path is relative to the common path (ie, "web/bundles" = 2 levels)
if (count($startPathArr) === 1 && $startPathArr[0] === '') {
$depth = 0;
} else {
$depth = count($startPathArr) - $index;
}
// When we need to traverse from the start, and we are starting from a root path, don't add '../'
if ('/' === $startPath[0] && 0 === $index && 0 === $depth) {
$traverser = '';
} else {
// Repeated "../" for each level need to reach the common path
$traverser = str_repeat('../', $depth);
}
$endPathRemainder = implode('/', array_slice($endPathArr, $index));
// Construct $endPath from traversing to the common path, then to the remaining $endPath
$relativePath = $traverser.('' !== $endPathRemainder ? $endPathRemainder.'/' : '');
return '' === $relativePath ? './' : $relativePath;
}
/**
* Mirrors a directory to another.
*
* @param string $originDir The origin directory
* @param string $targetDir The target directory
* @param \Traversable $iterator A Traversable instance
* @param array $options An array of boolean options
* Valid options are:
* - $options['override'] Whether to override an existing file on copy or not (see copy())
* - $options['copy_on_windows'] Whether to copy files instead of links on Windows (see symlink())
* - $options['delete'] Whether to delete files that are not in the source directory (defaults to false)
*
* @throws IOException When file type is unknown
*/
public function mirror($originDir, $targetDir, \Traversable $iterator = null, $options = array())
{
$targetDir = rtrim($targetDir, '/\\');
$originDir = rtrim($originDir, '/\\');
// Iterate in destination folder to remove obsolete entries
if ($this->exists($targetDir) && isset($options['delete']) && $options['delete']) {
$deleteIterator = $iterator;
if (null === $deleteIterator) {
$flags = \FilesystemIterator::SKIP_DOTS;
$deleteIterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($targetDir, $flags), \RecursiveIteratorIterator::CHILD_FIRST);
}
foreach ($deleteIterator as $file) {
$origin = str_replace($targetDir, $originDir, $file->getPathname());
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 = str_replace($originDir, $targetDir, $file->getPathname());
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);
}
}
}
}
/**
* Returns whether the file path is an absolute path.
*
* @param string $file A file path
*
* @return bool
*/
public function isAbsolutePath($file)
{
return strspn($file, '/\\', 0, 1)
|| (strlen($file) > 3 && ctype_alpha($file[0])
&& substr($file, 1, 1) === ':'
&& strspn($file, '/\\', 2, 1)
)
|| null !== parse_url($file, PHP_URL_SCHEME)
;
}
/**
* Creates a temporary file with support for custom stream wrappers.
*
* @param string $dir The directory where the temporary filename will be created
* @param string $prefix The prefix of the generated temporary filename
* Note: Windows uses only the first three characters of prefix
*
* @return string The new temporary filename (with path), or throw an exception on failure
*/
public function tempnam($dir, $prefix)
{
list($scheme, $hierarchy) = $this->getSchemeAndHierarchy($dir);
// If no scheme or scheme is "file" or "gs" (Google Cloud) create temp file in local filesystem
if (null === $scheme || 'file' === $scheme || 'gs' === $scheme) {
$tmpFile = @tempnam($hierarchy, $prefix);
// If tempnam failed or no scheme return the filename otherwise prepend the scheme
if (false !== $tmpFile) {
if (null !== $scheme && 'gs' !== $scheme) {
return $scheme.'://'.$tmpFile;
}
return $tmpFile;
}
throw new IOException('A temporary file could not be created.');
}
// Loop until we create a valid temp file or have reached 10 attempts
for ($i = 0; $i < 10; ++$i) {
// Create a unique filename
$tmpFile = $dir.'/'.$prefix.uniqid(mt_rand(), true);
// Use fopen instead of file_exists as some streams do not support stat
// Use mode 'x+' to atomically check existence and create to avoid a TOCTOU vulnerability
$handle = @fopen($tmpFile, 'x+');
// If unsuccessful restart the loop
if (false === $handle) {
continue;
}
// Close the file if it was successfully opened
@fclose($handle);
return $tmpFile;
}
throw new IOException('A temporary file could not be created.');
}
/**
* Atomically dumps content into a file.
*
* @param string $filename The file to be written to
* @param string $content The data to write into the file
*
* @throws IOException If the file cannot be written to.
*/
public function dumpFile($filename, $content)
{
$dir = dirname($filename);
if (!is_dir($dir)) {
$this->mkdir($dir);
} elseif (!is_writable($dir)) {
throw new IOException(sprintf('Unable to write to the "%s" directory.', $dir), 0, null, $dir);
}
// Will create a temp file with 0600 access rights
// when the filesystem supports chmod.
$tmpFile = $this->tempnam($dir, basename($filename));
if (false === @file_put_contents($tmpFile, $content)) {
throw new IOException(sprintf('Failed to write file "%s".', $filename), 0, null, $filename);
}
@chmod($tmpFile, 0666 & ~umask());
$this->rename($tmpFile, $filename, true);
}
/**
* @param mixed $files
*
* @return \Traversable
*/
private function toIterator($files)
{
if (!$files instanceof \Traversable) {
$files = new \ArrayObject(is_array($files) ? $files : array($files));
}
return $files;
}
/**
* Gets a 2-tuple of scheme (may be null) and hierarchical part of a filename (e.g. file:///tmp -> array(file, tmp)).
*
* @param string $filename The filename to be parsed
*
* @return array The filename scheme and hierarchical part
*/
private function getSchemeAndHierarchy($filename)
{
$components = explode('://', $filename, 2);
return 2 === count($components) ? array($components[0], $components[1]) : array(null, $components[0]);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Filesystem;
use Symfony\Component\Filesystem\Exception\IOException;
/**
* LockHandler class provides a simple abstraction to lock anything by means of
* a file lock.
*
* A locked file is created based on the lock name when calling lock(). Other
* lock handlers will not be able to lock the same name until it is released
* (explicitly by calling release() or implicitly when the instance holding the
* lock is destroyed).
*
* @author Grégoire Pineau <lyrixx@lyrixx.info>
* @author Romain Neutron <imprec@gmail.com>
* @author Nicolas Grekas <p@tchwork.com>
*/
class LockHandler
{
private $file;
private $handle;
/**
* @param string $name The lock name
* @param string|null $lockPath The directory to store the lock. Default values will use temporary directory
*
* @throws IOException If the lock directory could not be created or is not writable
*/
public function __construct($name, $lockPath = null)
{
$lockPath = $lockPath ?: sys_get_temp_dir();
if (!is_dir($lockPath)) {
$fs = new Filesystem();
$fs->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));
}
/**
* Lock the resource.
*
* @param bool $blocking wait until the lock is released
*
* @return bool Returns true if the lock was acquired, false otherwise
*
* @throws IOException If the lock file could not be created or opened
*/
public function lock($blocking = false)
{
if ($this->handle) {
return true;
}
// Silence error reporting
set_error_handler(function () {});
if (!$this->handle = fopen($this->file, 'r')) {
if ($this->handle = fopen($this->file, 'x')) {
chmod($this->file, 0444);
} elseif (!$this->handle = fopen($this->file, 'r')) {
usleep(100); // Give some time for chmod() to complete
$this->handle = fopen($this->file, 'r');
}
}
restore_error_handler();
if (!$this->handle) {
$error = error_get_last();
throw new IOException($error['message'], 0, null, $this->file);
}
// On Windows, even if PHP doc says the contrary, LOCK_NB works, see
// https://bugs.php.net/54129
if (!flock($this->handle, LOCK_EX | ($blocking ? 0 : LOCK_NB))) {
fclose($this->handle);
$this->handle = null;
return false;
}
return true;
}
/**
* Release the resource.
*/
public function release()
{
if ($this->handle) {
flock($this->handle, LOCK_UN | LOCK_NB);
fclose($this->handle);
$this->handle = null;
}
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Comparator;
/**
* Comparator.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class Comparator
{
private $target;
private $operator = '==';
/**
* Gets the target value.
*
* @return string The target value
*/
public function getTarget()
{
return $this->target;
}
/**
* Sets the target value.
*
* @param string $target The target value
*/
public function setTarget($target)
{
$this->target = $target;
}
/**
* Gets the comparison operator.
*
* @return string The operator
*/
public function getOperator()
{
return $this->operator;
}
/**
* Sets the comparison operator.
*
* @param string $operator A valid operator
*
* @throws \InvalidArgumentException
*/
public function setOperator($operator)
{
if (!$operator) {
$operator = '==';
}
if (!in_array($operator, array('>', '<', '>=', '<=', '==', '!='))) {
throw new \InvalidArgumentException(sprintf('Invalid operator "%s".', $operator));
}
$this->operator = $operator;
}
/**
* Tests against the target.
*
* @param mixed $test A test value
*
* @return bool
*/
public function test($test)
{
switch ($this->operator) {
case '>':
return $test > $this->target;
case '>=':
return $test >= $this->target;
case '<':
return $test < $this->target;
case '<=':
return $test <= $this->target;
case '!=':
return $test != $this->target;
}
return $test == $this->target;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Comparator;
/**
* DateCompare compiles date comparisons.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class DateComparator extends Comparator
{
/**
* Constructor.
*
* @param string $test A comparison string
*
* @throws \InvalidArgumentException If the test is not understood
*/
public function __construct($test)
{
if (!preg_match('#^\s*(==|!=|[<>]=?|after|since|before|until)?\s*(.+?)\s*$#i', $test, $matches)) {
throw new \InvalidArgumentException(sprintf('Don\'t understand "%s" as a date test.', $test));
}
try {
$date = new \DateTime($matches[2]);
$target = $date->format('U');
} catch (\Exception $e) {
throw new \InvalidArgumentException(sprintf('"%s" is not a valid date.', $matches[2]));
}
$operator = isset($matches[1]) ? $matches[1] : '==';
if ('since' === $operator || 'after' === $operator) {
$operator = '>';
}
if ('until' === $operator || 'before' === $operator) {
$operator = '<';
}
$this->setOperator($operator);
$this->setTarget($target);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Comparator;
/**
* NumberComparator compiles a simple comparison to an anonymous
* subroutine, which you can call with a value to be tested again.
*
* Now this would be very pointless, if NumberCompare didn't understand
* magnitudes.
*
* The target value may use magnitudes of kilobytes (k, ki),
* megabytes (m, mi), or gigabytes (g, gi). Those suffixed
* with an i use the appropriate 2**n version in accordance with the
* IEC standard: http://physics.nist.gov/cuu/Units/binary.html
*
* Based on the Perl Number::Compare module.
*
* @author Fabien Potencier <fabien@symfony.com> PHP port
* @author Richard Clamp <richardc@unixbeard.net> Perl version
* @copyright 2004-2005 Fabien Potencier <fabien@symfony.com>
* @copyright 2002 Richard Clamp <richardc@unixbeard.net>
*
* @see http://physics.nist.gov/cuu/Units/binary.html
*/
class NumberComparator extends Comparator
{
/**
* Constructor.
*
* @param string $test A comparison string
*
* @throws \InvalidArgumentException If the test is not understood
*/
public function __construct($test)
{
if (!preg_match('#^\s*(==|!=|[<>]=?)?\s*([0-9\.]+)\s*([kmg]i?)?\s*$#i', $test, $matches)) {
throw new \InvalidArgumentException(sprintf('Don\'t understand "%s" as a number test.', $test));
}
$target = $matches[2];
if (!is_numeric($target)) {
throw new \InvalidArgumentException(sprintf('Invalid number "%s".', $target));
}
if (isset($matches[3])) {
// magnitude
switch (strtolower($matches[3])) {
case 'k':
$target *= 1000;
break;
case 'ki':
$target *= 1024;
break;
case 'm':
$target *= 1000000;
break;
case 'mi':
$target *= 1024 * 1024;
break;
case 'g':
$target *= 1000000000;
break;
case 'gi':
$target *= 1024 * 1024 * 1024;
break;
}
}
$this->setTarget($target);
$this->setOperator(isset($matches[1]) ? $matches[1] : '==');
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Exception;
/**
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
*/
class AccessDeniedException extends \UnexpectedValueException
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Exception;
/**
* @author Jean-François Simon <contact@jfsimon.fr>
*/
interface ExceptionInterface
{
/**
* @return \Symfony\Component\Finder\Adapter\AdapterInterface
*/
public function getAdapter();
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder;
use Symfony\Component\Finder\Comparator\DateComparator;
use Symfony\Component\Finder\Comparator\NumberComparator;
use Symfony\Component\Finder\Iterator\CustomFilterIterator;
use Symfony\Component\Finder\Iterator\DateRangeFilterIterator;
use Symfony\Component\Finder\Iterator\DepthRangeFilterIterator;
use Symfony\Component\Finder\Iterator\ExcludeDirectoryFilterIterator;
use Symfony\Component\Finder\Iterator\FilecontentFilterIterator;
use Symfony\Component\Finder\Iterator\FilenameFilterIterator;
use Symfony\Component\Finder\Iterator\SizeRangeFilterIterator;
use Symfony\Component\Finder\Iterator\SortableIterator;
/**
* Finder allows to build rules to find files and directories.
*
* It is a thin wrapper around several specialized iterator classes.
*
* All rules may be invoked several times.
*
* All methods return the current Finder object to allow easy chaining:
*
* $finder = Finder::create()->files()->name('*.php')->in(__DIR__);
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class Finder implements \IteratorAggregate, \Countable
{
const IGNORE_VCS_FILES = 1;
const IGNORE_DOT_FILES = 2;
private $mode = 0;
private $names = array();
private $notNames = array();
private $exclude = array();
private $filters = array();
private $depths = array();
private $sizes = array();
private $followLinks = false;
private $sort = false;
private $ignore = 0;
private $dirs = array();
private $dates = array();
private $iterators = array();
private $contains = array();
private $notContains = array();
private $paths = array();
private $notPaths = array();
private $ignoreUnreadableDirs = false;
private static $vcsPatterns = array('.svn', '_svn', 'CVS', '_darcs', '.arch-params', '.monotone', '.bzr', '.git', '.hg');
/**
* Constructor.
*/
public function __construct()
{
$this->ignore = static::IGNORE_VCS_FILES | static::IGNORE_DOT_FILES;
}
/**
* Creates a new Finder.
*
* @return Finder A new Finder instance
*/
public static function create()
{
return new static();
}
/**
* Restricts the matching to directories only.
*
* @return Finder|SplFileInfo[] The current Finder instance
*/
public function directories()
{
$this->mode = Iterator\FileTypeFilterIterator::ONLY_DIRECTORIES;
return $this;
}
/**
* Restricts the matching to files only.
*
* @return Finder|SplFileInfo[] The current Finder instance
*/
public function files()
{
$this->mode = Iterator\FileTypeFilterIterator::ONLY_FILES;
return $this;
}
/**
* Adds tests for the directory depth.
*
* Usage:
*
* $finder->depth('> 1') // the Finder will start matching at level 1.
* $finder->depth('< 3') // the Finder will descend at most 3 levels of directories below the starting point.
*
* @param int $level The depth level expression
*
* @return Finder|SplFileInfo[] The current Finder instance
*
* @see DepthRangeFilterIterator
* @see NumberComparator
*/
public function depth($level)
{
$this->depths[] = new Comparator\NumberComparator($level);
return $this;
}
/**
* Adds tests for file dates (last modified).
*
* The date must be something that strtotime() is able to parse:
*
* $finder->date('since yesterday');
* $finder->date('until 2 days ago');
* $finder->date('> now - 2 hours');
* $finder->date('>= 2005-10-15');
*
* @param string $date A date range string
*
* @return Finder|SplFileInfo[] The current Finder instance
*
* @see strtotime
* @see DateRangeFilterIterator
* @see DateComparator
*/
public function date($date)
{
$this->dates[] = new Comparator\DateComparator($date);
return $this;
}
/**
* Adds rules that files must match.
*
* You can use patterns (delimited with / sign), globs or simple strings.
*
* $finder->name('*.php')
* $finder->name('/\.php$/') // same as above
* $finder->name('test.php')
*
* @param string $pattern A pattern (a regexp, a glob, or a string)
*
* @return Finder|SplFileInfo[] The current Finder instance
*
* @see FilenameFilterIterator
*/
public function name($pattern)
{
$this->names[] = $pattern;
return $this;
}
/**
* Adds rules that files must not match.
*
* @param string $pattern A pattern (a regexp, a glob, or a string)
*
* @return Finder|SplFileInfo[] The current Finder instance
*
* @see FilenameFilterIterator
*/
public function notName($pattern)
{
$this->notNames[] = $pattern;
return $this;
}
/**
* Adds tests that file contents must match.
*
* Strings or PCRE patterns can be used:
*
* $finder->contains('Lorem ipsum')
* $finder->contains('/Lorem ipsum/i')
*
* @param string $pattern A pattern (string or regexp)
*
* @return Finder|SplFileInfo[] The current Finder instance
*
* @see FilecontentFilterIterator
*/
public function contains($pattern)
{
$this->contains[] = $pattern;
return $this;
}
/**
* Adds tests that file contents must not match.
*
* Strings or PCRE patterns can be used:
*
* $finder->notContains('Lorem ipsum')
* $finder->notContains('/Lorem ipsum/i')
*
* @param string $pattern A pattern (string or regexp)
*
* @return Finder|SplFileInfo[] The current Finder instance
*
* @see FilecontentFilterIterator
*/
public function notContains($pattern)
{
$this->notContains[] = $pattern;
return $this;
}
/**
* Adds rules that filenames must match.
*
* You can use patterns (delimited with / sign) or simple strings.
*
* $finder->path('some/special/dir')
* $finder->path('/some\/special\/dir/') // same as above
*
* Use only / as dirname separator.
*
* @param string $pattern A pattern (a regexp or a string)
*
* @return Finder|SplFileInfo[] The current Finder instance
*
* @see FilenameFilterIterator
*/
public function path($pattern)
{
$this->paths[] = $pattern;
return $this;
}
/**
* Adds rules that filenames must not match.
*
* You can use patterns (delimited with / sign) or simple strings.
*
* $finder->notPath('some/special/dir')
* $finder->notPath('/some\/special\/dir/') // same as above
*
* Use only / as dirname separator.
*
* @param string $pattern A pattern (a regexp or a string)
*
* @return Finder|SplFileInfo[] The current Finder instance
*
* @see FilenameFilterIterator
*/
public function notPath($pattern)
{
$this->notPaths[] = $pattern;
return $this;
}
/**
* Adds tests for file sizes.
*
* $finder->size('> 10K');
* $finder->size('<= 1Ki');
* $finder->size(4);
*
* @param string $size A size range string
*
* @return Finder|SplFileInfo[] The current Finder instance
*
* @see SizeRangeFilterIterator
* @see NumberComparator
*/
public function size($size)
{
$this->sizes[] = new Comparator\NumberComparator($size);
return $this;
}
/**
* Excludes directories.
*
* @param string|array $dirs A directory path or an array of directories
*
* @return Finder|SplFileInfo[] The current Finder instance
*
* @see ExcludeDirectoryFilterIterator
*/
public function exclude($dirs)
{
$this->exclude = array_merge($this->exclude, (array) $dirs);
return $this;
}
/**
* Excludes "hidden" directories and files (starting with a dot).
*
* @param bool $ignoreDotFiles Whether to exclude "hidden" files or not
*
* @return Finder|SplFileInfo[] The current Finder instance
*
* @see ExcludeDirectoryFilterIterator
*/
public function ignoreDotFiles($ignoreDotFiles)
{
if ($ignoreDotFiles) {
$this->ignore |= static::IGNORE_DOT_FILES;
} else {
$this->ignore &= ~static::IGNORE_DOT_FILES;
}
return $this;
}
/**
* Forces the finder to ignore version control directories.
*
* @param bool $ignoreVCS Whether to exclude VCS files or not
*
* @return Finder|SplFileInfo[] The current Finder instance
*
* @see ExcludeDirectoryFilterIterator
*/
public function ignoreVCS($ignoreVCS)
{
if ($ignoreVCS) {
$this->ignore |= static::IGNORE_VCS_FILES;
} else {
$this->ignore &= ~static::IGNORE_VCS_FILES;
}
return $this;
}
/**
* Adds VCS patterns.
*
* @see ignoreVCS()
*
* @param string|string[] $pattern VCS patterns to ignore
*/
public static function addVCSPattern($pattern)
{
foreach ((array) $pattern as $p) {
self::$vcsPatterns[] = $p;
}
self::$vcsPatterns = array_unique(self::$vcsPatterns);
}
/**
* Sorts files and directories by an anonymous function.
*
* The anonymous function receives two \SplFileInfo instances to compare.
*
* This can be slow as all the matching files and directories must be retrieved for comparison.
*
* @param \Closure $closure An anonymous function
*
* @return Finder|SplFileInfo[] The current Finder instance
*
* @see SortableIterator
*/
public function sort(\Closure $closure)
{
$this->sort = $closure;
return $this;
}
/**
* Sorts files and directories by name.
*
* This can be slow as all the matching files and directories must be retrieved for comparison.
*
* @return Finder|SplFileInfo[] The current Finder instance
*
* @see SortableIterator
*/
public function sortByName()
{
$this->sort = Iterator\SortableIterator::SORT_BY_NAME;
return $this;
}
/**
* Sorts files and directories by type (directories before files), then by name.
*
* This can be slow as all the matching files and directories must be retrieved for comparison.
*
* @return Finder|SplFileInfo[] The current Finder instance
*
* @see SortableIterator
*/
public function sortByType()
{
$this->sort = Iterator\SortableIterator::SORT_BY_TYPE;
return $this;
}
/**
* Sorts files and directories by the last accessed time.
*
* This is the time that the file was last accessed, read or written to.
*
* This can be slow as all the matching files and directories must be retrieved for comparison.
*
* @return Finder|SplFileInfo[] The current Finder instance
*
* @see SortableIterator
*/
public function sortByAccessedTime()
{
$this->sort = Iterator\SortableIterator::SORT_BY_ACCESSED_TIME;
return $this;
}
/**
* Sorts files and directories by the last inode changed time.
*
* This is the time that the inode information was last modified (permissions, owner, group or other metadata).
*
* On Windows, since inode is not available, changed time is actually the file creation time.
*
* This can be slow as all the matching files and directories must be retrieved for comparison.
*
* @return Finder|SplFileInfo[] The current Finder instance
*
* @see SortableIterator
*/
public function sortByChangedTime()
{
$this->sort = Iterator\SortableIterator::SORT_BY_CHANGED_TIME;
return $this;
}
/**
* Sorts files and directories by the last modified time.
*
* This is the last time the actual contents of the file were last modified.
*
* This can be slow as all the matching files and directories must be retrieved for comparison.
*
* @return Finder|SplFileInfo[] The current Finder instance
*
* @see SortableIterator
*/
public function sortByModifiedTime()
{
$this->sort = Iterator\SortableIterator::SORT_BY_MODIFIED_TIME;
return $this;
}
/**
* Filters the iterator with an anonymous function.
*
* The anonymous function receives a \SplFileInfo and must return false
* to remove files.
*
* @param \Closure $closure An anonymous function
*
* @return Finder|SplFileInfo[] The current Finder instance
*
* @see CustomFilterIterator
*/
public function filter(\Closure $closure)
{
$this->filters[] = $closure;
return $this;
}
/**
* Forces the following of symlinks.
*
* @return Finder|SplFileInfo[] The current Finder instance
*/
public function followLinks()
{
$this->followLinks = true;
return $this;
}
/**
* Tells finder to ignore unreadable directories.
*
* By default, scanning unreadable directories content throws an AccessDeniedException.
*
* @param bool $ignore
*
* @return Finder|SplFileInfo[] The current Finder instance
*/
public function ignoreUnreadableDirs($ignore = true)
{
$this->ignoreUnreadableDirs = (bool) $ignore;
return $this;
}
/**
* Searches files and directories which match defined rules.
*
* @param string|array $dirs A directory path or an array of directories
*
* @return Finder|SplFileInfo[] The current Finder instance
*
* @throws \InvalidArgumentException if one of the directories does not exist
*/
public function in($dirs)
{
$resolvedDirs = array();
foreach ((array) $dirs as $dir) {
if (is_dir($dir)) {
$resolvedDirs[] = $dir;
} elseif ($glob = glob($dir, (defined('GLOB_BRACE') ? GLOB_BRACE : 0) | GLOB_ONLYDIR)) {
$resolvedDirs = array_merge($resolvedDirs, $glob);
} else {
throw new \InvalidArgumentException(sprintf('The "%s" directory does not exist.', $dir));
}
}
$this->dirs = array_merge($this->dirs, $resolvedDirs);
return $this;
}
/**
* Returns an Iterator for the current Finder configuration.
*
* This method implements the IteratorAggregate interface.
*
* @return \Iterator|SplFileInfo[] An iterator
*
* @throws \LogicException if the in() method has not been called
*/
public function getIterator()
{
if (0 === count($this->dirs) && 0 === count($this->iterators)) {
throw new \LogicException('You must call one of in() or append() methods before iterating over a Finder.');
}
if (1 === count($this->dirs) && 0 === count($this->iterators)) {
return $this->searchInDirectory($this->dirs[0]);
}
$iterator = new \AppendIterator();
foreach ($this->dirs as $dir) {
$iterator->append($this->searchInDirectory($dir));
}
foreach ($this->iterators as $it) {
$iterator->append($it);
}
return $iterator;
}
/**
* Appends an existing set of files/directories to the finder.
*
* The set can be another Finder, an Iterator, an IteratorAggregate, or even a plain array.
*
* @param mixed $iterator
*
* @return Finder|SplFileInfo[] The finder
*
* @throws \InvalidArgumentException When the given argument is not iterable.
*/
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;
}
/**
* Counts all the results collected by the iterators.
*
* @return int
*/
public function count()
{
return iterator_count($this->getIterator());
}
/**
* @param $dir
*
* @return \Iterator
*/
private function searchInDirectory($dir)
{
if (static::IGNORE_VCS_FILES === (static::IGNORE_VCS_FILES & $this->ignore)) {
$this->exclude = array_merge($this->exclude, self::$vcsPatterns);
}
if (static::IGNORE_DOT_FILES === (static::IGNORE_DOT_FILES & $this->ignore)) {
$this->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 ($this->exclude) {
$iterator = new Iterator\ExcludeDirectoryFilterIterator($iterator, $this->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 || $this->notPaths) {
$iterator = new Iterator\PathFilterIterator($iterator, $this->paths, $this->notPaths);
}
if ($this->sort) {
$iteratorAggregate = new Iterator\SortableIterator($iterator, $this->sort);
$iterator = $iteratorAggregate->getIterator();
}
return $iterator;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder;
/**
* Glob matches globbing patterns against text.
*
* if match_glob("foo.*", "foo.bar") echo "matched\n";
*
* // prints foo.bar and foo.baz
* $regex = glob_to_regex("foo.*");
* for (array('foo.bar', 'foo.baz', 'foo', 'bar') as $t)
* {
* if (/$regex/) echo "matched: $car\n";
* }
*
* Glob implements glob(3) style matching that can be used to match
* against text, rather than fetching names from a filesystem.
*
* Based on the Perl Text::Glob module.
*
* @author Fabien Potencier <fabien@symfony.com> PHP port
* @author Richard Clamp <richardc@unixbeard.net> Perl version
* @copyright 2004-2005 Fabien Potencier <fabien@symfony.com>
* @copyright 2002 Richard Clamp <richardc@unixbeard.net>
*/
class Glob
{
/**
* Returns a regexp which is the equivalent of the glob pattern.
*
* @param string $glob The glob pattern
* @param bool $strictLeadingDot
* @param bool $strictWildcardSlash
* @param string $delimiter Optional delimiter
*
* @return string regex The regexp
*/
public static function toRegex($glob, $strictLeadingDot = true, $strictWildcardSlash = true, $delimiter = '#')
{
$firstByte = true;
$escaping = false;
$inCurlies = 0;
$regex = '';
$sizeGlob = strlen($glob);
for ($i = 0; $i < $sizeGlob; ++$i) {
$car = $glob[$i];
if ($firstByte) {
if ($strictLeadingDot && '.' !== $car) {
$regex .= '(?=[^\.])';
}
$firstByte = false;
}
if ('/' === $car) {
$firstByte = true;
}
if ($delimiter === $car || '.' === $car || '(' === $car || ')' === $car || '|' === $car || '+' === $car || '^' === $car || '$' === $car) {
$regex .= "\\$car";
} elseif ('*' === $car) {
$regex .= $escaping ? '\\*' : ($strictWildcardSlash ? '[^/]*' : '.*');
} elseif ('?' === $car) {
$regex .= $escaping ? '\\?' : ($strictWildcardSlash ? '[^/]' : '.');
} elseif ('{' === $car) {
$regex .= $escaping ? '\\{' : '(';
if (!$escaping) {
++$inCurlies;
}
} elseif ('}' === $car && $inCurlies) {
$regex .= $escaping ? '}' : ')';
if (!$escaping) {
--$inCurlies;
}
} elseif (',' === $car && $inCurlies) {
$regex .= $escaping ? ',' : '|';
} elseif ('\\' === $car) {
if ($escaping) {
$regex .= '\\\\';
$escaping = false;
} else {
$escaping = true;
}
continue;
} else {
$regex .= $car;
}
$escaping = false;
}
return $delimiter.'^'.$regex.'$'.$delimiter;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Iterator;
/**
* CustomFilterIterator filters files by applying anonymous functions.
*
* The anonymous function receives a \SplFileInfo and must return false
* to remove files.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class CustomFilterIterator extends FilterIterator
{
private $filters = array();
/**
* Constructor.
*
* @param \Iterator $iterator The Iterator to filter
* @param callable[] $filters An array of PHP callbacks
*
* @throws \InvalidArgumentException
*/
public function __construct(\Iterator $iterator, array $filters)
{
foreach ($filters as $filter) {
if (!is_callable($filter)) {
throw new \InvalidArgumentException('Invalid PHP callback.');
}
}
$this->filters = $filters;
parent::__construct($iterator);
}
/**
* Filters the iterator values.
*
* @return bool true if the value should be kept, false otherwise
*/
public function accept()
{
$fileinfo = $this->current();
foreach ($this->filters as $filter) {
if (false === call_user_func($filter, $fileinfo)) {
return false;
}
}
return true;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Iterator;
use Symfony\Component\Finder\Comparator\DateComparator;
/**
* DateRangeFilterIterator filters out files that are not in the given date range (last modified dates).
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class DateRangeFilterIterator extends FilterIterator
{
private $comparators = array();
/**
* Constructor.
*
* @param \Iterator $iterator The Iterator to filter
* @param DateComparator[] $comparators An array of DateComparator instances
*/
public function __construct(\Iterator $iterator, array $comparators)
{
$this->comparators = $comparators;
parent::__construct($iterator);
}
/**
* Filters the iterator values.
*
* @return bool true if the value should be kept, false otherwise
*/
public function accept()
{
$fileinfo = $this->current();
if (!file_exists($fileinfo->getRealPath())) {
return false;
}
$filedate = $fileinfo->getMTime();
foreach ($this->comparators as $compare) {
if (!$compare->test($filedate)) {
return false;
}
}
return true;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Iterator;
/**
* DepthRangeFilterIterator limits the directory depth.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class DepthRangeFilterIterator extends FilterIterator
{
private $minDepth = 0;
/**
* Constructor.
*
* @param \RecursiveIteratorIterator $iterator The Iterator to filter
* @param int $minDepth The min depth
* @param int $maxDepth The max depth
*/
public function __construct(\RecursiveIteratorIterator $iterator, $minDepth = 0, $maxDepth = PHP_INT_MAX)
{
$this->minDepth = $minDepth;
$iterator->setMaxDepth(PHP_INT_MAX === $maxDepth ? -1 : $maxDepth);
parent::__construct($iterator);
}
/**
* Filters the iterator values.
*
* @return bool true if the value should be kept, false otherwise
*/
public function accept()
{
return $this->getInnerIterator()->getDepth() >= $this->minDepth;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Iterator;
/**
* ExcludeDirectoryFilterIterator filters out directories.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class ExcludeDirectoryFilterIterator extends FilterIterator implements \RecursiveIterator
{
private $iterator;
private $isRecursive;
private $excludedDirs = array();
private $excludedPattern;
/**
* Constructor.
*
* @param \Iterator $iterator The Iterator to filter
* @param array $directories An array of directories to exclude
*/
public function __construct(\Iterator $iterator, array $directories)
{
$this->iterator = $iterator;
$this->isRecursive = $iterator instanceof \RecursiveIterator;
$patterns = array();
foreach ($directories as $directory) {
$directory = rtrim($directory, '/');
if (!$this->isRecursive || false !== strpos($directory, '/')) {
$patterns[] = preg_quote($directory, '#');
} else {
$this->excludedDirs[$directory] = true;
}
}
if ($patterns) {
$this->excludedPattern = '#(?:^|/)(?:'.implode('|', $patterns).')(?:/|$)#';
}
parent::__construct($iterator);
}
/**
* Filters the iterator values.
*
* @return bool True if the value should be kept, false otherwise
*/
public function accept()
{
if ($this->isRecursive && isset($this->excludedDirs[$this->getFilename()]) && $this->isDir()) {
return false;
}
if ($this->excludedPattern) {
$path = $this->isDir() ? $this->current()->getRelativePathname() : $this->current()->getRelativePath();
$path = str_replace('\\', '/', $path);
return !preg_match($this->excludedPattern, $path);
}
return true;
}
public function hasChildren()
{
return $this->isRecursive && $this->iterator->hasChildren();
}
public function getChildren()
{
$children = new self($this->iterator->getChildren(), array());
$children->excludedDirs = $this->excludedDirs;
$children->excludedPattern = $this->excludedPattern;
return $children;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Iterator;
/**
* FilecontentFilterIterator filters files by their contents using patterns (regexps or strings).
*
* @author Fabien Potencier <fabien@symfony.com>
* @author WÅodzimierz Gajda <gajdaw@gajdaw.pl>
*/
class FilecontentFilterIterator extends MultiplePcreFilterIterator
{
/**
* Filters the iterator values.
*
* @return bool true if the value should be kept, false otherwise
*/
public function accept()
{
if (!$this->matchRegexps && !$this->noMatchRegexps) {
return true;
}
$fileinfo = $this->current();
if ($fileinfo->isDir() || !$fileinfo->isReadable()) {
return false;
}
$content = $fileinfo->getContents();
if (!$content) {
return false;
}
return $this->isAccepted($content);
}
/**
* Converts string to regexp if necessary.
*
* @param string $str Pattern: string or regexp
*
* @return string regexp corresponding to a given string or regexp
*/
protected function toRegex($str)
{
return $this->isRegex($str) ? $str : '/'.preg_quote($str, '/').'/';
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Iterator;
use Symfony\Component\Finder\Glob;
/**
* FilenameFilterIterator filters files by patterns (a regexp, a glob, or a string).
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class FilenameFilterIterator extends MultiplePcreFilterIterator
{
/**
* Filters the iterator values.
*
* @return bool true if the value should be kept, false otherwise
*/
public function accept()
{
return $this->isAccepted($this->current()->getFilename());
}
/**
* Converts glob to regexp.
*
* PCRE patterns are left unchanged.
* Glob strings are transformed with Glob::toRegex().
*
* @param string $str Pattern: glob or regexp
*
* @return string regexp corresponding to a given glob or regexp
*/
protected function toRegex($str)
{
return $this->isRegex($str) ? $str : Glob::toRegex($str);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Iterator;
/**
* FileTypeFilterIterator only keeps files, directories, or both.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class FileTypeFilterIterator extends FilterIterator
{
const ONLY_FILES = 1;
const ONLY_DIRECTORIES = 2;
private $mode;
/**
* Constructor.
*
* @param \Iterator $iterator The Iterator to filter
* @param int $mode The mode (self::ONLY_FILES or self::ONLY_DIRECTORIES)
*/
public function __construct(\Iterator $iterator, $mode)
{
$this->mode = $mode;
parent::__construct($iterator);
}
/**
* Filters the iterator values.
*
* @return bool true if the value should be kept, false otherwise
*/
public function accept()
{
$fileinfo = $this->current();
if (self::ONLY_DIRECTORIES === (self::ONLY_DIRECTORIES & $this->mode) && $fileinfo->isFile()) {
return false;
} elseif (self::ONLY_FILES === (self::ONLY_FILES & $this->mode) && $fileinfo->isDir()) {
return false;
}
return true;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Iterator;
/**
* This iterator just overrides the rewind method in order to correct a PHP bug,
* which existed before version 5.5.23/5.6.7.
*
* @see https://bugs.php.net/68557
*
* @author Alex Bogomazov
*/
abstract class FilterIterator extends \FilterIterator
{
/**
* This is a workaround for the problem with \FilterIterator leaving inner \FilesystemIterator in wrong state after
* rewind in some cases.
*
* @see FilterIterator::rewind()
*/
public function rewind()
{
if (PHP_VERSION_ID > 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) {
// this condition is necessary for iterators to work properly with non-local filesystems like ftp
if ($innerIterator->isRewindable()) {
$innerIterator->next();
$innerIterator->rewind();
}
} elseif ($innerIterator instanceof \FilesystemIterator) {
$innerIterator->next();
$innerIterator->rewind();
}
$iterator = $innerIterator;
}
parent::rewind();
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Iterator;
/**
* MultiplePcreFilterIterator filters files using patterns (regexps, globs or strings).
*
* @author Fabien Potencier <fabien@symfony.com>
*/
abstract class MultiplePcreFilterIterator extends FilterIterator
{
protected $matchRegexps = array();
protected $noMatchRegexps = array();
/**
* Constructor.
*
* @param \Iterator $iterator The Iterator to filter
* @param array $matchPatterns An array of patterns that need to match
* @param array $noMatchPatterns An array of patterns that need to not match
*/
public function __construct(\Iterator $iterator, array $matchPatterns, array $noMatchPatterns)
{
foreach ($matchPatterns as $pattern) {
$this->matchRegexps[] = $this->toRegex($pattern);
}
foreach ($noMatchPatterns as $pattern) {
$this->noMatchRegexps[] = $this->toRegex($pattern);
}
parent::__construct($iterator);
}
/**
* Checks whether the string is accepted by the regex filters.
*
* If there is no regexps defined in the class, this method will accept the string.
* Such case can be handled by child classes before calling the method if they want to
* apply a different behavior.
*
* @param string $string The string to be matched against filters
*
* @return bool
*/
protected function isAccepted($string)
{
// should at least not match one rule to exclude
foreach ($this->noMatchRegexps as $regex) {
if (preg_match($regex, $string)) {
return false;
}
}
// should at least match one rule
if ($this->matchRegexps) {
foreach ($this->matchRegexps as $regex) {
if (preg_match($regex, $string)) {
return true;
}
}
return false;
}
// If there is no match rules, the file is accepted
return true;
}
/**
* Checks whether the string is a regex.
*
* @param string $str
*
* @return bool Whether the given string is a regex
*/
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 (array(array('{', '}'), array('(', ')'), array('[', ']'), array('<', '>')) as $delimiters) {
if ($start === $delimiters[0] && $end === $delimiters[1]) {
return true;
}
}
}
return false;
}
/**
* Converts string into regexp.
*
* @param string $str Pattern
*
* @return string regexp corresponding to a given string
*/
abstract protected function toRegex($str);
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Iterator;
/**
* PathFilterIterator filters files by path patterns (e.g. some/special/dir).
*
* @author Fabien Potencier <fabien@symfony.com>
* @author WÅodzimierz Gajda <gajdaw@gajdaw.pl>
*/
class PathFilterIterator extends MultiplePcreFilterIterator
{
/**
* Filters the iterator values.
*
* @return bool true if the value should be kept, false otherwise
*/
public function accept()
{
$filename = $this->current()->getRelativePathname();
if ('\\' === DIRECTORY_SEPARATOR) {
$filename = str_replace('\\', '/', $filename);
}
return $this->isAccepted($filename);
}
/**
* Converts strings to regexp.
*
* PCRE patterns are left unchanged.
*
* Default conversion:
* 'lorem/ipsum/dolor' ==> 'lorem\/ipsum\/dolor/'
*
* Use only / as directory separator (on Windows also).
*
* @param string $str Pattern: regexp or dirname
*
* @return string regexp corresponding to a given string or regexp
*/
protected function toRegex($str)
{
return $this->isRegex($str) ? $str : '/'.preg_quote($str, '/').'/';
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Iterator;
use Symfony\Component\Finder\Exception\AccessDeniedException;
use Symfony\Component\Finder\SplFileInfo;
/**
* Extends the \RecursiveDirectoryIterator to support relative paths.
*
* @author Victor Berchet <victor@suumit.com>
*/
class RecursiveDirectoryIterator extends \RecursiveDirectoryIterator
{
/**
* @var bool
*/
private $ignoreUnreadableDirs;
/**
* @var bool
*/
private $rewindable;
// these 3 properties take part of the performance optimization to avoid redoing the same work in all iterations
private $rootPath;
private $subPath;
private $directorySeparator = '/';
/**
* Constructor.
*
* @param string $path
* @param int $flags
* @param bool $ignoreUnreadableDirs
*
* @throws \RuntimeException
*/
public function __construct($path, $flags, $ignoreUnreadableDirs = false)
{
if ($flags & (self::CURRENT_AS_PATHNAME | self::CURRENT_AS_SELF)) {
throw new \RuntimeException('This iterator only support returning current as fileinfo.');
}
parent::__construct($path, $flags);
$this->ignoreUnreadableDirs = $ignoreUnreadableDirs;
$this->rootPath = $path;
if ('/' !== DIRECTORY_SEPARATOR && !($flags & self::UNIX_PATHS)) {
$this->directorySeparator = DIRECTORY_SEPARATOR;
}
}
/**
* Return an instance of SplFileInfo with support for relative paths.
*
* @return SplFileInfo File information
*/
public function current()
{
// the logic here avoids redoing the same work in all iterations
if (null === $subPathname = $this->subPath) {
$subPathname = $this->subPath = (string) $this->getSubPath();
}
if ('' !== $subPathname) {
$subPathname .= $this->directorySeparator;
}
$subPathname .= $this->getFilename();
return new SplFileInfo($this->rootPath.$this->directorySeparator.$subPathname, $this->subPath, $subPathname);
}
/**
* @return \RecursiveIterator
*
* @throws AccessDeniedException
*/
public function getChildren()
{
try {
$children = parent::getChildren();
if ($children instanceof self) {
// parent method will call the constructor with default arguments, so unreadable dirs won't be ignored anymore
$children->ignoreUnreadableDirs = $this->ignoreUnreadableDirs;
// performance optimization to avoid redoing the same work in all children
$children->rewindable = &$this->rewindable;
$children->rootPath = $this->rootPath;
}
return $children;
} catch (\UnexpectedValueException $e) {
if ($this->ignoreUnreadableDirs) {
// If directory is unreadable and finder is set to ignore it, a fake empty content is returned.
return new \RecursiveArrayIterator(array());
} else {
throw new AccessDeniedException($e->getMessage(), $e->getCode(), $e);
}
}
}
/**
* Do nothing for non rewindable stream.
*/
public function rewind()
{
if (false === $this->isRewindable()) {
return;
}
// @see https://bugs.php.net/68557
if (PHP_VERSION_ID < 50523 || PHP_VERSION_ID >= 50600 && PHP_VERSION_ID < 50607) {
parent::next();
}
parent::rewind();
}
/**
* Checks if the stream is rewindable.
*
* @return bool true when the stream is rewindable, false otherwise
*/
public function isRewindable()
{
if (null !== $this->rewindable) {
return $this->rewindable;
}
// workaround for an HHVM bug, should be removed when https://github.com/facebook/hhvm/issues/7281 is fixed
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;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Iterator;
use Symfony\Component\Finder\Comparator\NumberComparator;
/**
* SizeRangeFilterIterator filters out files that are not in the given size range.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class SizeRangeFilterIterator extends FilterIterator
{
private $comparators = array();
/**
* Constructor.
*
* @param \Iterator $iterator The Iterator to filter
* @param NumberComparator[] $comparators An array of NumberComparator instances
*/
public function __construct(\Iterator $iterator, array $comparators)
{
$this->comparators = $comparators;
parent::__construct($iterator);
}
/**
* Filters the iterator values.
*
* @return bool true if the value should be kept, false otherwise
*/
public function accept()
{
$fileinfo = $this->current();
if (!$fileinfo->isFile()) {
return true;
}
$filesize = $fileinfo->getSize();
foreach ($this->comparators as $compare) {
if (!$compare->test($filesize)) {
return false;
}
}
return true;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Iterator;
/**
* SortableIterator applies a sort on a given Iterator.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class SortableIterator implements \IteratorAggregate
{
const SORT_BY_NAME = 1;
const SORT_BY_TYPE = 2;
const SORT_BY_ACCESSED_TIME = 3;
const SORT_BY_CHANGED_TIME = 4;
const SORT_BY_MODIFIED_TIME = 5;
private $iterator;
private $sort;
/**
* Constructor.
*
* @param \Traversable $iterator The Iterator to filter
* @param int|callable $sort The sort type (SORT_BY_NAME, SORT_BY_TYPE, or a PHP callback)
*
* @throws \InvalidArgumentException
*/
public function __construct(\Traversable $iterator, $sort)
{
$this->iterator = $iterator;
if (self::SORT_BY_NAME === $sort) {
$this->sort = function ($a, $b) {
return strcmp($a->getRealpath(), $b->getRealpath());
};
} elseif (self::SORT_BY_TYPE === $sort) {
$this->sort = function ($a, $b) {
if ($a->isDir() && $b->isFile()) {
return -1;
} elseif ($a->isFile() && $b->isDir()) {
return 1;
}
return strcmp($a->getRealpath(), $b->getRealpath());
};
} elseif (self::SORT_BY_ACCESSED_TIME === $sort) {
$this->sort = function ($a, $b) {
return $a->getATime() - $b->getATime();
};
} elseif (self::SORT_BY_CHANGED_TIME === $sort) {
$this->sort = function ($a, $b) {
return $a->getCTime() - $b->getCTime();
};
} elseif (self::SORT_BY_MODIFIED_TIME === $sort) {
$this->sort = 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);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder;
/**
* Extends \SplFileInfo to support relative paths.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class SplFileInfo extends \SplFileInfo
{
private $relativePath;
private $relativePathname;
/**
* Constructor.
*
* @param string $file The file name
* @param string $relativePath The relative path
* @param string $relativePathname The relative path name
*/
public function __construct($file, $relativePath, $relativePathname)
{
parent::__construct($file);
$this->relativePath = $relativePath;
$this->relativePathname = $relativePathname;
}
/**
* Returns the relative path.
*
* This path does not contain the file name.
*
* @return string the relative path
*/
public function getRelativePath()
{
return $this->relativePath;
}
/**
* Returns the relative path name.
*
* This path contains the file name.
*
* @return string the relative path name
*/
public function getRelativePathname()
{
return $this->relativePathname;
}
/**
* Returns the contents of the file.
*
* @return string the contents of the file
*
* @throws \RuntimeException
*/
public function getContents()
{
$level = error_reporting(0);
$content = file_get_contents($this->getPathname());
error_reporting($level);
if (false === $content) {
$error = error_get_last();
throw new \RuntimeException($error['message']);
}
return $content;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Symfony\Polyfill\Mbstring as p;
if (!function_exists('mb_strlen')) {
define('MB_CASE_UPPER', 0);
define('MB_CASE_LOWER', 1);
define('MB_CASE_TITLE', 2);
function mb_convert_encoding($s, $to, $from = null) { return p\Mbstring::mb_convert_encoding($s, $to, $from); }
function mb_decode_mimeheader($s) { return p\Mbstring::mb_decode_mimeheader($s); }
function mb_encode_mimeheader($s, $charset = null, $transferEnc = null, $lf = null, $indent = null) { return p\Mbstring::mb_encode_mimeheader($s, $charset, $transferEnc, $lf, $indent); }
function mb_convert_case($s, $mode, $enc = null) { return p\Mbstring::mb_convert_case($s, $mode, $enc); }
function mb_internal_encoding($enc = null) { return p\Mbstring::mb_internal_encoding($enc); }
function mb_language($lang = null) { return p\Mbstring::mb_language($lang); }
function mb_list_encodings() { return p\Mbstring::mb_list_encodings(); }
function mb_encoding_aliases($encoding) { return p\Mbstring::mb_encoding_aliases($encoding); }
function mb_check_encoding($var = null, $encoding = null) { return p\Mbstring::mb_check_encoding($var, $encoding); }
function mb_detect_encoding($str, $encodingList = null, $strict = false) { return p\Mbstring::mb_detect_encoding($str, $encodingList, $strict); }
function mb_detect_order($encodingList = null) { return p\Mbstring::mb_detect_order($encodingList); }
function mb_parse_str($s, &$result = array()) { parse_str($s, $result); }
function mb_strlen($s, $enc = null) { return p\Mbstring::mb_strlen($s, $enc); }
function mb_strpos($s, $needle, $offset = 0, $enc = null) { return p\Mbstring::mb_strpos($s, $needle, $offset, $enc); }
function mb_strtolower($s, $enc = null) { return p\Mbstring::mb_strtolower($s, $enc); }
function mb_strtoupper($s, $enc = null) { return p\Mbstring::mb_strtoupper($s, $enc); }
function mb_substitute_character($char = null) { return p\Mbstring::mb_substitute_character($char); }
function mb_substr($s, $start, $length = 2147483647, $enc = null) { return p\Mbstring::mb_substr($s, $start, $length, $enc); }
function mb_stripos($s, $needle, $offset = 0, $enc = null) { return p\Mbstring::mb_stripos($s, $needle, $offset, $enc); }
function mb_stristr($s, $needle, $part = false, $enc = null) { return p\Mbstring::mb_stristr($s, $needle, $part, $enc); }
function mb_strrchr($s, $needle, $part = false, $enc = null) { return p\Mbstring::mb_strrchr($s, $needle, $part, $enc); }
function mb_strrichr($s, $needle, $part = false, $enc = null) { return p\Mbstring::mb_strrichr($s, $needle, $part, $enc); }
function mb_strripos($s, $needle, $offset = 0, $enc = null) { return p\Mbstring::mb_strripos($s, $needle, $offset, $enc); }
function mb_strrpos($s, $needle, $offset = 0, $enc = null) { return p\Mbstring::mb_strrpos($s, $needle, $offset, $enc); }
function mb_strstr($s, $needle, $part = false, $enc = null) { return p\Mbstring::mb_strstr($s, $needle, $part, $enc); }
function mb_get_info($type = 'all') { return p\Mbstring::mb_get_info($type); }
function mb_http_output($enc = null) { return p\Mbstring::mb_http_output($enc); }
function mb_strwidth($s, $enc = null) { return p\Mbstring::mb_strwidth($s, $enc); }
function mb_substr_count($haystack, $needle, $enc = null) { return p\Mbstring::mb_substr_count($haystack, $needle, $enc); }
function mb_output_handler($contents, $status) { return p\Mbstring::mb_output_handler($contents, $status); }
function mb_http_input($type = '') { return p\Mbstring::mb_http_input($type); }
function mb_convert_variables($toEncoding, $fromEncoding, &$a = null, &$b = null, &$c = null, &$d = null, &$e = null, &$f = null) { return p\Mbstring::mb_convert_variables($toEncoding, $fromEncoding, $a, $b, $c, $d, $e, $f); }
}
if (!function_exists('mb_chr')) {
function mb_ord($s, $enc = null) { return p\Mbstring::mb_ord($s, $enc); }
function mb_chr($code, $enc = null) { return p\Mbstring::mb_chr($code, $enc); }
function mb_scrub($s, $enc = null) { $enc = null === $enc ? mb_internal_encoding() : $enc; return mb_convert_encoding($s, $enc, $enc); }
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Polyfill\Mbstring;
/**
* Partial mbstring implementation in PHP, iconv based, UTF-8 centric.
*
* Implemented:
* - mb_chr - Returns a specific character from its Unicode code point
* - mb_convert_encoding - Convert character encoding
* - mb_convert_variables - Convert character code in variable(s)
* - mb_decode_mimeheader - Decode string in MIME header field
* - mb_encode_mimeheader - Encode string for MIME header XXX NATIVE IMPLEMENTATION IS REALLY BUGGED
* - mb_convert_case - Perform case folding on a string
* - mb_get_info - Get internal settings of mbstring
* - mb_http_input - Detect HTTP input character encoding
* - mb_http_output - Set/Get HTTP output character encoding
* - mb_internal_encoding - Set/Get internal character encoding
* - mb_list_encodings - Returns an array of all supported encodings
* - mb_ord - Returns the Unicode code point of a character
* - mb_output_handler - Callback function converts character encoding in output buffer
* - mb_scrub - Replaces ill-formed byte sequences with substitute characters
* - mb_strlen - Get string length
* - mb_strpos - Find position of first occurrence of string in a string
* - mb_strrpos - Find position of last occurrence of a string in a string
* - mb_strtolower - Make a string lowercase
* - mb_strtoupper - Make a string uppercase
* - mb_substitute_character - Set/Get substitution character
* - mb_substr - Get part of string
* - mb_stripos - Finds position of first occurrence of a string within another, case insensitive
* - mb_stristr - Finds first occurrence of a string within another, case insensitive
* - mb_strrchr - Finds the last occurrence of a character in a string within another
* - mb_strrichr - Finds the last occurrence of a character in a string within another, case insensitive
* - mb_strripos - Finds position of last occurrence of a string within another, case insensitive
* - mb_strstr - Finds first occurrence of a string within anothers
* - mb_strwidth - Return width of string
* - mb_substr_count - Count the number of substring occurrences
*
* Not implemented:
* - mb_convert_kana - Convert "kana" one from another ("zen-kaku", "han-kaku" and more)
* - mb_decode_numericentity - Decode HTML numeric string reference to character
* - mb_encode_numericentity - Encode character to HTML numeric string reference
* - mb_ereg_* - Regular expression with multibyte support
* - mb_parse_str - Parse GET/POST/COOKIE data and set global variable
* - mb_preferred_mime_name - Get MIME charset string
* - mb_regex_encoding - Returns current encoding for multibyte regex as string
* - mb_regex_set_options - Set/Get the default options for mbregex functions
* - mb_send_mail - Send encoded mail
* - mb_split - Split multibyte string using regular expression
* - mb_strcut - Get part of string
* - mb_strimwidth - Get truncated string with specified width
*
* @author Nicolas Grekas <p@tchwork.com>
*
* @internal
*/
final class Mbstring
{
const MB_CASE_FOLD = PHP_INT_MAX;
private static $encodingList = array('ASCII', 'UTF-8');
private static $language = 'neutral';
private static $internalEncoding = 'UTF-8';
private static $caseFold = array(
array('µ','ſ',"\xCD\x85",'ς',"\xCF\x90","\xCF\x91","\xCF\x95","\xCF\x96","\xCF\xB0","\xCF\xB1","\xCF\xB5","\xE1\xBA\x9B","\xE1\xBE\xBE"),
array('μ','s','ι', 'σ','β', 'θ', 'φ', 'Ï€', 'κ', 'Ï<>', 'ε', "\xE1\xB9\xA1",'ι'),
);
public static function mb_convert_encoding($s, $toEncoding, $fromEncoding = null)
{
if (is_array($fromEncoding) || false !== strpos($fromEncoding, ',')) {
$fromEncoding = self::mb_detect_encoding($s, $fromEncoding);
} else {
$fromEncoding = self::getEncoding($fromEncoding);
}
$toEncoding = self::getEncoding($toEncoding);
if ('BASE64' === $fromEncoding) {
$s = base64_decode($s);
$fromEncoding = $toEncoding;
}
if ('BASE64' === $toEncoding) {
return base64_encode($s);
}
if ('HTML-ENTITIES' === $toEncoding || 'HTML' === $toEncoding) {
if ('HTML-ENTITIES' === $fromEncoding || 'HTML' === $fromEncoding) {
$fromEncoding = 'Windows-1252';
}
if ('UTF-8' !== $fromEncoding) {
$s = iconv($fromEncoding, 'UTF-8//IGNORE', $s);
}
return preg_replace_callback('/[\x80-\xFF]+/', array(__CLASS__, 'html_encoding_callback'), $s);
}
if ('HTML-ENTITIES' === $fromEncoding) {
$s = html_entity_decode($s, ENT_COMPAT, 'UTF-8');
$fromEncoding = 'UTF-8';
}
return iconv($fromEncoding, $toEncoding.'//IGNORE', $s);
}
public static function mb_convert_variables($toEncoding, $fromEncoding, &$a = null, &$b = null, &$c = null, &$d = null, &$e = null, &$f = null)
{
$vars = array(&$a, &$b, &$c, &$d, &$e, &$f);
$ok = true;
array_walk_recursive($vars, function (&$v) use (&$ok, $toEncoding, $fromEncoding) {
if (false === $v = Mbstring::mb_convert_encoding($v, $toEncoding, $fromEncoding)) {
$ok = false;
}
});
return $ok ? $fromEncoding : false;
}
public static function mb_decode_mimeheader($s)
{
return iconv_mime_decode($s, 2, self::$internalEncoding);
}
public static function mb_encode_mimeheader($s, $charset = null, $transferEncoding = null, $linefeed = null, $indent = null)
{
trigger_error('mb_encode_mimeheader() is bugged. Please use iconv_mime_encode() instead', E_USER_WARNING);
}
public static function mb_convert_case($s, $mode, $encoding = null)
{
if ('' === $s .= '') {
return '';
}
$encoding = self::getEncoding($encoding);
if ('UTF-8' === $encoding) {
$encoding = null;
} else {
$s = iconv($encoding, 'UTF-8//IGNORE', $s);
}
if (MB_CASE_TITLE == $mode) {
$s = preg_replace_callback('/\b\p{Ll}/u', array(__CLASS__, 'title_case_upper'), $s);
$s = preg_replace_callback('/\B[\p{Lu}\p{Lt}]+/u', array(__CLASS__, 'title_case_lower'), $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)
{
switch ($encoding = self::getEncoding($encoding)) {
case 'ASCII':
case 'CP850':
return strlen($s);
}
return @iconv_strlen($s, $encoding);
}
public static function mb_strpos($haystack, $needle, $offset = 0, $encoding = null)
{
$encoding = self::getEncoding($encoding);
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 ($offset != (int) $offset) {
$offset = 0;
} elseif ($offset = (int) $offset) {
if ($offset < 0) {
$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_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 ($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 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);
$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);
}
$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($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_lower($s)
{
return self::mb_convert_case($s[0], MB_CASE_LOWER, 'UTF-8');
}
private static function title_case_upper($s)
{
return self::mb_convert_case($s[0], MB_CASE_UPPER, '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;
}
$encoding = strtoupper($encoding);
if ('8BIT' === $encoding || 'BINARY' === $encoding) {
return 'CP850';
}
if ('UTF8' === $encoding) {
return 'UTF-8';
}
return $encoding;
}
}
<?php
static $data = array (
'A' => 'a',
'B' => 'b',
'C' => 'c',
'D' => 'd',
'E' => 'e',
'F' => 'f',
'G' => 'g',
'H' => 'h',
'I' => 'i',
'J' => 'j',
'K' => 'k',
'L' => 'l',
'M' => 'm',
'N' => 'n',
'O' => 'o',
'P' => 'p',
'Q' => 'q',
'R' => 'r',
'S' => 's',
'T' => 't',
'U' => 'u',
'V' => 'v',
'W' => 'w',
'X' => 'x',
'Y' => 'y',
'Z' => 'z',
'À' => 'à',
<>' => 'á',
'Â' => 'â',
'Ã' => 'ã',
'Ä' => 'ä',
'Ã…' => 'Ã¥',
'Æ' => 'æ',
'Ç' => 'ç',
'È' => 'è',
'É' => 'é',
'Ê' => 'ê',
'Ë' => 'ë',
'Ì' => 'ì',
<>' => 'í',
'Î' => 'î',
<>' => 'ï',
<>' => 'ð',
'Ñ' => 'ñ',
'Ò' => 'ò',
'Ó' => 'ó',
'Ô' => 'ô',
'Õ' => 'õ',
'Ö' => 'ö',
'Ø' => 'ø',
'Ù' => 'ù',
'Ú' => 'ú',
'Û' => 'û',
'Ü' => 'ü',
<>' => 'ý',
'Þ' => 'þ',
'Ä€' => 'Ä<>',
'Ă' => 'ă',
'Ä„' => 'Ä…',
'Ć' => 'ć',
'Ĉ' => 'ĉ',
'ÄŠ' => 'Ä‹',
'ÄŒ' => 'Ä<>',
'ÄŽ' => 'Ä<>',
<>' => 'Ä‘',
'Ä’' => 'Ä“',
'Ä”' => 'Ä•',
'Ä–' => 'Ä—',
'Ę' => 'ę',
'Äš' => 'Ä›',
'Äœ' => 'Ä<>',
'Äž' => 'ÄŸ',
'Ä ' => 'Ä¡',
'Ä¢' => 'Ä£',
'Ĥ' => 'ĥ',
'Ħ' => 'ħ',
'Ĩ' => 'ĩ',
'Ī' => 'ī',
'Ĭ' => 'ĭ',
'Į' => 'į',
'İ' => 'i',
'IJ' => 'ij',
'Ĵ' => 'ĵ',
'Ķ' => 'ķ',
'Ĺ' => 'ĺ',
'Ļ' => 'ļ',
'Ľ' => 'ľ',
'Ä¿' => 'Å€',
<>' => 'Å‚',
'Ń' => 'ń',
'Ņ' => 'ņ',
'Ň' => 'ň',
'ÅŠ' => 'Å‹',
'ÅŒ' => 'Å<>',
'ÅŽ' => 'Å<>',
<>' => 'Å‘',
'Å’' => 'Å“',
'Å”' => 'Å•',
'Å–' => 'Å—',
'Ř' => 'ř',
'Åš' => 'Å›',
'Åœ' => 'Å<>',
'Åž' => 'ÅŸ',
'Å ' => 'Å¡',
'Å¢' => 'Å£',
'Ť' => 'ť',
'Ŧ' => 'ŧ',
'Ũ' => 'ũ',
'Ū' => 'ū',
'Ŭ' => 'ŭ',
'Ů' => 'ů',
'Ű' => 'ű',
'Ų' => 'ų',
'Ŵ' => 'ŵ',
'Ŷ' => 'ŷ',
'Ÿ' => 'ÿ',
'Ź' => 'ź',
'Ż' => 'ż',
'Ž' => 'ž',
<>' => 'É“',
'Ƃ' => 'ƃ',
'Æ„' => 'Æ…',
'Ɔ' => 'ɔ',
'Ƈ' => 'ƈ',
'Ɖ' => 'ɖ',
'ÆŠ' => 'É—',
'Ƌ' => 'ƌ',
'ÆŽ' => 'Ç<>',
<>' => 'É™',
<>' => 'É›',
'Æ‘' => 'Æ’',
'Æ“' => 'É ',
'Æ”' => 'É£',
'Æ–' => 'É©',
'Ɨ' => 'ɨ',
'Ƙ' => 'ƙ',
'Ɯ' => 'ɯ',
<>' => 'ɲ',
'Ɵ' => 'ɵ',
'Æ ' => 'Æ¡',
'Æ¢' => 'Æ£',
'Ƥ' => 'ƥ',
'Ʀ' => 'ʀ',
'Ƨ' => 'ƨ',
'Ʃ' => 'ʃ',
'Ƭ' => 'ƭ',
'Ʈ' => 'ʈ',
'Ư' => 'ư',
'Ʊ' => 'ʊ',
'Ʋ' => 'ʋ',
'Ƴ' => 'ƴ',
'Ƶ' => 'ƶ',
'Æ·' => 'Ê’',
'Ƹ' => 'ƹ',
'Ƽ' => 'ƽ',
'DŽ' => 'dž',
'Dž' => 'dž',
'LJ' => 'lj',
'Lj' => 'lj',
'NJ' => 'nj',
'Nj' => 'nj',
<>' => 'ÇŽ',
<>' => 'Ç<>',
'Ç‘' => 'Ç’',
'Ç“' => 'Ç”',
'Ç•' => 'Ç–',
'Ǘ' => 'ǘ',
'Ç™' => 'Çš',
'Ǜ' => 'ǜ',
'Çž' => 'ÇŸ',
'Ç ' => 'Ç¡',
'Ç¢' => 'Ç£',
'Ǥ' => 'ǥ',
'Ǧ' => 'ǧ',
'Ǩ' => 'ǩ',
'Ǫ' => 'ǫ',
'Ǭ' => 'ǭ',
'Ǯ' => 'ǯ',
'DZ' => 'dz',
'Dz' => 'dz',
'Ǵ' => 'ǵ',
'Ƕ' => 'ƕ',
'Ç·' => 'Æ¿',
'Ǹ' => 'ǹ',
'Ǻ' => 'ǻ',
'Ǽ' => 'ǽ',
'Ǿ' => 'ǿ',
'È€' => 'È<>',
'Ȃ' => 'ȃ',
'È„' => 'È…',
'Ȇ' => 'ȇ',
'Ȉ' => 'ȉ',
'ÈŠ' => 'È‹',
'ÈŒ' => 'È<>',
'ÈŽ' => 'È<>',
<>' => 'È‘',
'È’' => 'È“',
'È”' => 'È•',
'È–' => 'È—',
'Ș' => 'ș',
'Èš' => 'È›',
'Èœ' => 'È<>',
'Èž' => 'ÈŸ',
'È ' => 'Æž',
'È¢' => 'È£',
'Ȥ' => 'ȥ',
'Ȧ' => 'ȧ',
'Ȩ' => 'ȩ',
'Ȫ' => 'ȫ',
'Ȭ' => 'ȭ',
'Ȯ' => 'ȯ',
'Ȱ' => 'ȱ',
'Ȳ' => 'ȳ',
'Ⱥ' => 'ⱥ',
'Ȼ' => 'ȼ',
'Ƚ' => 'ƚ',
'Ⱦ' => 'ⱦ',
<>' => 'É‚',
'Ƀ' => 'ƀ',
'Ʉ' => 'ʉ',
'Ʌ' => 'ʌ',
'Ɇ' => 'ɇ',
'Ɉ' => 'ɉ',
'ÉŠ' => 'É‹',
'ÉŒ' => 'É<>',
'ÉŽ' => 'É<>',
'Ͱ' => 'ͱ',
'Ͳ' => 'ͳ',
'Ͷ' => 'ͷ',
'Ϳ' => 'ϳ',
'Ά' => 'ά',
'Έ' => 'έ',
'Ή' => 'ή',
'Ί' => 'ί',
'Ό' => 'ό',
'ÎŽ' => 'Ï<>',
<>' => 'ÏŽ',
'Α' => 'α',
'Β' => 'β',
'Γ' => 'γ',
'Δ' => 'δ',
'Ε' => 'ε',
'Ζ' => 'ζ',
'Η' => 'η',
'Θ' => 'θ',
'Ι' => 'ι',
'Κ' => 'κ',
'Λ' => 'λ',
'Μ' => 'μ',
<>' => 'ν',
'Ξ' => 'ξ',
'Ο' => 'ο',
'Π' => 'π',
'Ρ' => 'Ï<>',
'Σ' => 'σ',
'Τ' => 'τ',
'Î¥' => 'Ï…',
'Φ' => 'φ',
'Χ' => 'χ',
'Ψ' => 'ψ',
'Ω' => 'ω',
'Ϊ' => 'ϊ',
'Ϋ' => 'ϋ',
<>' => 'Ï—',
'Ϙ' => 'ϙ',
'Ïš' => 'Ï›',
'Ïœ' => 'Ï<>',
'Ïž' => 'ÏŸ',
'Ï ' => 'Ï¡',
'Ï¢' => 'Ï£',
'Ϥ' => 'ϥ',
'Ϧ' => 'ϧ',
'Ϩ' => 'ϩ',
'Ϫ' => 'ϫ',
'Ϭ' => 'ϭ',
'Ϯ' => 'ϯ',
'ϴ' => 'θ',
'Ϸ' => 'ϸ',
'Ϲ' => 'ϲ',
'Ϻ' => 'ϻ',
'Ͻ' => 'ͻ',
'Ͼ' => 'ͼ',
'Ͽ' => 'ͽ',
'Ѐ' => 'Ñ<>',
<>' => 'Ñ‘',
'Ђ' => 'ђ',
'Ѓ' => 'ѓ',
'Є' => 'є',
'Ð…' => 'Ñ•',
'І' => 'і',
'Ї' => 'ї',
'Ј' => 'ј',
'Љ' => 'љ',
'Њ' => 'њ',
'Ћ' => 'ћ',
'Ќ' => 'ќ',
<>' => 'Ñ<>',
'ÐŽ' => 'Ñž',
<>' => 'ÑŸ',
<>' => 'а',
'Б' => 'б',
'В' => 'в',
'Г' => 'г',
'Д' => 'д',
'Е' => 'е',
'Ж' => 'ж',
'З' => 'з',
'И' => 'и',
'Й' => 'й',
'К' => 'к',
'Л' => 'л',
'М' => 'м',
<>' => 'н',
'О' => 'о',
'П' => 'п',
'Р' => 'р',
'С' => 'Ñ<>',
'Т' => 'т',
'У' => 'у',
'Ф' => 'ф',
'Ð¥' => 'Ñ…',
'Ц' => 'ц',
'Ч' => 'ч',
'Ш' => 'ш',
'Щ' => 'щ',
'Ъ' => 'ъ',
'Ы' => 'ы',
'Ь' => 'ь',
'Э' => 'Ñ<>',
'Ю' => 'ю',
'Я' => 'Ñ<>',
'Ñ ' => 'Ñ¡',
'Ñ¢' => 'Ñ£',
'Ѥ' => 'ѥ',
'Ѧ' => 'ѧ',
'Ѩ' => 'ѩ',
'Ѫ' => 'ѫ',
'Ѭ' => 'ѭ',
'Ѯ' => 'ѯ',
'Ѱ' => 'ѱ',
'Ѳ' => 'ѳ',
'Ѵ' => 'ѵ',
'Ѷ' => 'ѷ',
'Ѹ' => 'ѹ',
'Ѻ' => 'ѻ',
'Ѽ' => 'ѽ',
'Ѿ' => 'ѿ',
'Ò€' => 'Ò<>',
'ÒŠ' => 'Ò‹',
'ÒŒ' => 'Ò<>',
'ÒŽ' => 'Ò<>',
<>' => 'Ò‘',
'Ò’' => 'Ò“',
'Ò”' => 'Ò•',
'Ò–' => 'Ò—',
'Ò˜' => 'Ò™',
'Òš' => 'Ò›',
'Òœ' => 'Ò<>',
'Òž' => 'ÒŸ',
'Ò ' => 'Ò¡',
'Ò¢' => 'Ò£',
'Ò¤' => 'Ò¥',
'Ò¦' => 'Ò§',
'Ò¨' => 'Ò©',
'Òª' => 'Ò«',
'Ò¬' => 'Ò­',
'Ò®' => 'Ò¯',
'Ò°' => 'Ò±',
'Ò²' => 'Ò³',
'Ò´' => 'Òµ',
'Ò¶' => 'Ò·',
'Ò¸' => 'Ò¹',
'Òº' => 'Ò»',
'Ò¼' => 'Ò½',
'Ò¾' => 'Ò¿',
'Ó€' => 'Ó<>',
<>' => 'Ó‚',
'Óƒ' => 'Ó„',
'Ó…' => 'Ó†',
'Ó‡' => 'Óˆ',
'Ó‰' => 'ÓŠ',
'Ӌ' => 'ӌ',
<>' => 'ÓŽ',
<>' => 'Ó‘',
'Ó’' => 'Ó“',
'Ó”' => 'Ó•',
'Ó–' => 'Ó—',
'Ó˜' => 'Ó™',
'Óš' => 'Ó›',
'Óœ' => 'Ó<>',
'Óž' => 'ÓŸ',
'Ó ' => 'Ó¡',
'Ó¢' => 'Ó£',
'Ó¤' => 'Ó¥',
'Ó¦' => 'Ó§',
'Ó¨' => 'Ó©',
'Óª' => 'Ó«',
'Ó¬' => 'Ó­',
'Ó®' => 'Ó¯',
'Ó°' => 'Ó±',
'Ó²' => 'Ó³',
'Ó´' => 'Óµ',
'Ó¶' => 'Ó·',
'Ó¸' => 'Ó¹',
'Óº' => 'Ó»',
'Ó¼' => 'Ó½',
'Ó¾' => 'Ó¿',
'Ô€' => 'Ô<>',
'Ô‚' => 'Ôƒ',
'Ô„' => 'Ô…',
'Ô†' => 'Ô‡',
'Ôˆ' => 'Ô‰',
'ÔŠ' => 'Ô‹',
'ÔŒ' => 'Ô<>',
'ÔŽ' => 'Ô<>',
<>' => 'Ô‘',
'Ô’' => 'Ô“',
'Ô”' => 'Ô•',
'Ô–' => 'Ô—',
'Ô˜' => 'Ô™',
'Ôš' => 'Ô›',
'Ôœ' => 'Ô<>',
'Ôž' => 'ÔŸ',
'Ô ' => 'Ô¡',
'Ô¢' => 'Ô£',
'Ô¤' => 'Ô¥',
'Ô¦' => 'Ô§',
'Ô¨' => 'Ô©',
'Ôª' => 'Ô«',
'Ô¬' => 'Ô­',
'Ô®' => 'Ô¯',
'Ô±' => 'Õ¡',
'Ô²' => 'Õ¢',
'Ô³' => 'Õ£',
'Ô´' => 'Õ¤',
'Ôµ' => 'Õ¥',
'Ô¶' => 'Õ¦',
'Ô·' => 'Õ§',
'Ô¸' => 'Õ¨',
'Ô¹' => 'Õ©',
'Ôº' => 'Õª',
'Ô»' => 'Õ«',
'Ô¼' => 'Õ¬',
'Ô½' => 'Õ­',
'Ô¾' => 'Õ®',
'Ô¿' => 'Õ¯',
'Õ€' => 'Õ°',
<>' => 'Õ±',
'Õ‚' => 'Õ²',
'Õƒ' => 'Õ³',
'Õ„' => 'Õ´',
'Õ…' => 'Õµ',
'Õ†' => 'Õ¶',
'Õ‡' => 'Õ·',
'Õˆ' => 'Õ¸',
'Õ‰' => 'Õ¹',
'ÕŠ' => 'Õº',
'Õ‹' => 'Õ»',
'Ռ' => 'ռ',
<>' => 'Õ½',
'ÕŽ' => 'Õ¾',
<>' => 'Õ¿',
<>' => 'Ö€',
'Õ‘' => 'Ö<>',
'Õ’' => 'Ö‚',
'Õ“' => 'Öƒ',
'Õ”' => 'Ö„',
'Õ•' => 'Ö…',
'Õ–' => 'Ö†',
'á‚ ' => 'â´€',
'á‚¡' => 'â´<C3A2>',
'á‚¢' => 'â´‚',
'á‚£' => 'â´ƒ',
'Ⴄ' => 'ⴄ',
'á‚¥' => 'â´…',
'Ⴆ' => 'ⴆ',
'á‚§' => 'â´‡',
'Ⴈ' => 'ⴈ',
'á‚©' => 'â´‰',
'Ⴊ' => 'ⴊ',
'á‚«' => 'â´‹',
'Ⴌ' => 'ⴌ',
'á‚­' => 'â´<C3A2>',
'á‚®' => 'â´Ž',
'Ⴏ' => 'â´<C3A2>',
'á‚°' => 'â´<C3A2>',
'Ⴑ' => 'ⴑ',
'Ⴒ' => 'ⴒ',
'Ⴓ' => 'ⴓ',
'á‚´' => 'â´”',
'Ⴕ' => 'ⴕ',
'á‚¶' => 'â´–',
'á‚·' => 'â´—',
'Ⴘ' => 'ⴘ',
'Ⴙ' => 'ⴙ',
'Ⴚ' => 'ⴚ',
'á‚»' => 'â´›',
'Ⴜ' => 'ⴜ',
'Ⴝ' => 'â´<C3A2>',
'Ⴞ' => 'ⴞ',
'á‚¿' => 'â´Ÿ',
'Ⴠ' => 'ⴠ',
'áƒ<C3A1>' => 'â´¡',
'Ⴢ' => 'ⴢ',
'Ⴣ' => 'ⴣ',
'Ⴤ' => 'ⴤ',
'Ⴥ' => 'ⴥ',
'Ⴧ' => 'ⴧ',
'áƒ<C3A1>' => 'â´­',
'Ḁ' => 'á¸<C3A1>',
'Ḃ' => 'ḃ',
'Ḅ' => 'ḅ',
'Ḇ' => 'ḇ',
'Ḉ' => 'ḉ',
'Ḋ' => 'ḋ',
'Ḍ' => 'á¸<C3A1>',
'Ḏ' => 'á¸<C3A1>',
¸<C3A1>' => 'ḑ',
'Ḓ' => 'ḓ',
'Ḕ' => 'ḕ',
'Ḗ' => 'ḗ',
'Ḙ' => 'ḙ',
'Ḛ' => 'ḛ',
'Ḝ' => 'á¸<C3A1>',
'Ḟ' => 'ḟ',
'Ḡ' => 'ḡ',
'Ḣ' => 'ḣ',
'Ḥ' => 'ḥ',
'Ḧ' => 'ḧ',
'Ḩ' => 'ḩ',
'Ḫ' => 'ḫ',
'Ḭ' => 'ḭ',
'Ḯ' => 'ḯ',
'Ḱ' => 'ḱ',
'Ḳ' => 'ḳ',
'Ḵ' => 'ḵ',
'Ḷ' => 'ḷ',
'Ḹ' => 'ḹ',
'Ḻ' => 'ḻ',
'Ḽ' => 'ḽ',
'Ḿ' => 'ḿ',
'á¹€' => 'á¹<C3A1>',
'Ṃ' => 'ṃ',
'Ṅ' => 'ṅ',
'Ṇ' => 'ṇ',
'Ṉ' => 'ṉ',
'Ṋ' => 'ṋ',
'Ṍ' => 'á¹<C3A1>',
'Ṏ' => 'á¹<C3A1>',
'á¹<C3A1>' => 'ṑ',
'Ṓ' => 'ṓ',
'Ṕ' => 'ṕ',
'á¹–' => 'á¹—',
'Ṙ' => 'ṙ',
'Ṛ' => 'ṛ',
'Ṝ' => 'á¹<C3A1>',
'Ṟ' => 'ṟ',
'Ṡ' => 'ṡ',
'á¹¢' => 'á¹£',
'Ṥ' => 'ṥ',
'Ṧ' => 'ṧ',
'Ṩ' => 'ṩ',
'Ṫ' => 'ṫ',
'Ṭ' => 'ṭ',
'Ṯ' => 'ṯ',
'á¹°' => 'á¹±',
'á¹²' => 'á¹³',
'á¹´' => 'á¹µ',
'á¹¶' => 'á¹·',
'Ṹ' => 'ṹ',
'Ṻ' => 'ṻ',
'á¹¼' => 'á¹½',
'Ṿ' => 'ṿ',
'Ẁ' => 'áº<C3A1>',
'Ẃ' => 'ẃ',
'Ẅ' => 'ẅ',
'Ẇ' => 'ẇ',
'Ẉ' => 'ẉ',
'Ẋ' => 'ẋ',
'Ẍ' => 'áº<C3A1>',
'Ẏ' => 'áº<C3A1>',
'áº<C3A1>' => 'ẑ',
'Ẓ' => 'ẓ',
'Ẕ' => 'ẕ',
'ẞ' => 'ß',
'Ạ' => 'ạ',
'Ả' => 'ả',
'Ấ' => 'ấ',
'Ầ' => 'ầ',
'Ẩ' => 'ẩ',
'Ẫ' => 'ẫ',
'Ậ' => 'ậ',
'Ắ' => 'ắ',
'Ằ' => 'ằ',
'Ẳ' => 'ẳ',
'Ẵ' => 'ẵ',
'Ặ' => 'ặ',
'Ẹ' => 'ẹ',
'Ẻ' => 'ẻ',
'Ẽ' => 'ẽ',
'Ế' => 'ế',
'Ề' => 'á»<C3A1>',
'Ể' => 'ể',
'Ễ' => 'ễ',
'Ệ' => 'ệ',
'Ỉ' => 'ỉ',
'Ị' => 'ị',
'Ọ' => 'á»<C3A1>',
'Ỏ' => 'á»<C3A1>',
'á»<C3A1>' => 'ố',
'Ồ' => 'ồ',
'Ổ' => 'ổ',
'á»–' => 'á»—',
'Ộ' => 'ộ',
'Ớ' => 'ớ',
'Ờ' => 'á»<C3A1>',
'Ở' => 'ở',
'Ỡ' => 'ỡ',
'Ợ' => 'ợ',
'Ụ' => 'ụ',
'Ủ' => 'ủ',
'Ứ' => 'ứ',
'Ừ' => 'ừ',
'Ử' => 'ử',
'Ữ' => 'ữ',
'á»°' => 'á»±',
'Ỳ' => 'ỳ',
'Ỵ' => 'ỵ',
'á»¶' => 'á»·',
'Ỹ' => 'ỹ',
'Ỻ' => 'ỻ',
'Ỽ' => 'ỽ',
'Ỿ' => 'ỿ',
'Ἀ' => 'ἀ',
'Ἁ' => 'á¼<C3A1>',
'Ἂ' => 'ἂ',
'Ἃ' => 'ἃ',
'Ἄ' => 'ἄ',
'á¼<C3A1>' => 'á¼…',
'Ἆ' => 'ἆ',
'á¼<C3A1>' => 'ἇ',
'Ἐ' => 'á¼<C3A1>',
'Ἑ' => 'ἑ',
'Ἒ' => 'ἒ',
'Ἓ' => 'ἓ',
'Ἔ' => 'ἔ',
'á¼<C3A1>' => 'ἕ',
'Ἠ' => 'ἠ',
'Ἡ' => 'ἡ',
'Ἢ' => 'ἢ',
'Ἣ' => 'ἣ',
'Ἤ' => 'ἤ',
'á¼­' => 'á¼¥',
'Ἦ' => 'ἦ',
'Ἧ' => 'ἧ',
'Ἰ' => 'ἰ',
'á¼¹' => 'á¼±',
'Ἲ' => 'ἲ',
'á¼»' => 'á¼³',
'á¼¼' => 'á¼´',
'á¼½' => 'á¼µ',
'á¼¾' => 'á¼¶',
'Ἷ' => 'ἷ',
'Ὀ' => 'ὀ',
'Ὁ' => 'á½<C3A1>',
'Ὂ' => 'ὂ',
'Ὃ' => 'ὃ',
'Ὄ' => 'ὄ',
'á½<C3A1>' => 'á½…',
'Ὑ' => 'ὑ',
'Ὓ' => 'ὓ',
'á½<C3A1>' => 'ὕ',
'Ὗ' => 'ὗ',
'Ὠ' => 'ὠ',
'Ὡ' => 'ὡ',
'Ὢ' => 'ὢ',
'Ὣ' => 'ὣ',
'Ὤ' => 'ὤ',
'á½­' => 'á½¥',
'Ὦ' => 'ὦ',
'Ὧ' => 'ὧ',
'ᾈ' => 'ᾀ',
'ᾉ' => 'á¾<C3A1>',
'ᾊ' => 'ᾂ',
'ᾋ' => 'ᾃ',
'ᾌ' => 'ᾄ',
'á¾<C3A1>' => 'á¾…',
'ᾎ' => 'ᾆ',
'á¾<C3A1>' => 'ᾇ',
'ᾘ' => 'á¾<C3A1>',
'ᾙ' => 'ᾑ',
'ᾚ' => 'ᾒ',
'ᾛ' => 'ᾓ',
'ᾜ' => 'ᾔ',
'á¾<C3A1>' => 'ᾕ',
'ᾞ' => 'ᾖ',
'ᾟ' => 'ᾗ',
'ᾨ' => 'ᾠ',
'ᾩ' => 'ᾡ',
'ᾪ' => 'ᾢ',
'ᾫ' => 'ᾣ',
'ᾬ' => 'ᾤ',
'á¾­' => 'á¾¥',
'ᾮ' => 'ᾦ',
'ᾯ' => 'ᾧ',
'Ᾰ' => 'ᾰ',
'á¾¹' => 'á¾±',
'Ὰ' => 'ὰ',
'á¾»' => 'á½±',
'á¾¼' => 'á¾³',
'Ὲ' => 'ὲ',
'Έ' => 'έ',
'Ὴ' => 'ὴ',
'á¿‹' => 'á½µ',
'ῌ' => 'ῃ',
'Ῐ' => 'á¿<C3A1>',
'á¿™' => 'á¿‘',
'Ὶ' => 'ὶ',
'á¿›' => 'á½·',
'Ῠ' => 'ῠ',
'á¿©' => 'á¿¡',
'Ὺ' => 'ὺ',
'á¿«' => 'á½»',
'Ῥ' => 'ῥ',
'Ὸ' => 'ὸ',
'Ό' => 'ό',
'Ὼ' => 'ὼ',
'á¿»' => 'á½½',
'ῼ' => 'ῳ',
'Ω' => 'ω',
'K' => 'k',
'â„«' => 'Ã¥',
'Ⅎ' => 'ⅎ',
'â… ' => 'â…°',
'â…¡' => 'â…±',
'â…¢' => 'â…²',
'â…£' => 'â…³',
'â…¤' => 'â…´',
'â…¥' => 'â…µ',
'â…¦' => 'â…¶',
'â…§' => 'â…·',
'â…¨' => 'â…¸',
'â…©' => 'â…¹',
'â…ª' => 'â…º',
'â…«' => 'â…»',
'â…¬' => 'â…¼',
'â…­' => 'â…½',
'â…®' => 'â…¾',
'â…¯' => 'â…¿',
'Ↄ' => 'ↄ',
'â’¶' => 'â“<C3A2>',
'â’·' => 'â“‘',
'â’¸' => 'â“’',
'â’¹' => 'â““',
'â’º' => 'â“”',
'â’»' => 'â“•',
'â’¼' => 'â“–',
'â’½' => 'â“—',
'Ⓘ' => 'ⓘ',
'â’¿' => 'â“™',
'Ⓚ' => 'ⓚ',
'â“<C3A2>' => 'â“›',
'Ⓜ' => 'ⓜ',
'Ⓝ' => 'â“<C3A2>',
'Ⓞ' => 'ⓞ',
'Ⓟ' => 'ⓟ',
'Ⓠ' => 'ⓠ',
'Ⓡ' => 'ⓡ',
'Ⓢ' => 'ⓢ',
'Ⓣ' => 'ⓣ',
'Ⓤ' => 'ⓤ',
'â“‹' => 'â“¥',
'Ⓦ' => 'ⓦ',
'â“<C3A2>' => 'â“§',
'Ⓨ' => 'ⓨ',
'â“<C3A2>' => 'â“©',
'â°€' => 'â°°',
'â°<C3A2>' => 'â°±',
'â°‚' => 'â°²',
'â°ƒ' => 'â°³',
'â°„' => 'â°´',
'â°…' => 'â°µ',
'â°†' => 'â°¶',
'â°‡' => 'â°·',
'â°ˆ' => 'â°¸',
'â°‰' => 'â°¹',
'â°Š' => 'â°º',
'â°‹' => 'â°»',
'Ⰼ' => 'ⰼ',
'â°<C3A2>' => 'â°½',
'â°Ž' => 'â°¾',
'â°<C3A2>' => 'â°¿',
'â°<C3A2>' => 'â±€',
'â°‘' => 'â±<C3A2>',
'Ⱂ' => 'ⱂ',
'Ⱃ' => 'ⱃ',
'Ⱄ' => 'ⱄ',
'â°•' => 'â±…',
'Ⱆ' => 'ⱆ',
'Ⱇ' => 'ⱇ',
'Ⱈ' => 'ⱈ',
'Ⱉ' => 'ⱉ',
'Ⱊ' => 'ⱊ',
'Ⱋ' => 'ⱋ',
'Ⱌ' => 'ⱌ',
'â°<C3A2>' => 'â±<C3A2>',
'Ⱎ' => 'ⱎ',
'â°Ÿ' => 'â±<C3A2>',
'â° ' => 'â±<C3A2>',
'Ⱑ' => 'ⱑ',
'â°¢' => 'â±’',
'Ⱓ' => 'ⱓ',
'â°¤' => 'â±”',
'Ⱕ' => 'ⱕ',
'â°¦' => 'â±–',
'â°§' => 'â±—',
'Ⱘ' => 'ⱘ',
'â°©' => 'â±™',
'Ⱚ' => 'ⱚ',
'â°«' => 'â±›',
'Ⱜ' => 'ⱜ',
'â°­' => 'â±<C3A2>',
'Ⱞ' => 'ⱞ',
'Ⱡ' => 'ⱡ',
'â±¢' => 'É«',
'â±£' => 'áµ½',
'Ɽ' => 'ɽ',
'Ⱨ' => 'ⱨ',
'Ⱪ' => 'ⱪ',
'Ⱬ' => 'ⱬ',
'â±­' => 'É‘',
'Ɱ' => 'ɱ',
'Ɐ' => 'É<>',
'â±°' => 'É’',
'â±²' => 'â±³',
'â±µ' => 'â±¶',
'â±¾' => 'È¿',
'Ɀ' => 'ɀ',
'â²€' => 'â²<C3A2>',
'Ⲃ' => 'ⲃ',
'Ⲅ' => 'ⲅ',
'Ⲇ' => 'ⲇ',
'Ⲉ' => 'ⲉ',
'Ⲋ' => 'ⲋ',
'Ⲍ' => 'â²<C3A2>',
'Ⲏ' => 'â²<C3A2>',
'â²<C3A2>' => 'ⲑ',
'Ⲓ' => 'ⲓ',
'Ⲕ' => 'ⲕ',
'â²–' => 'â²—',
'Ⲙ' => 'ⲙ',
'Ⲛ' => 'ⲛ',
'Ⲝ' => 'â²<C3A2>',
'Ⲟ' => 'ⲟ',
'Ⲡ' => 'ⲡ',
'â²¢' => 'â²£',
'Ⲥ' => 'ⲥ',
'Ⲧ' => 'ⲧ',
'Ⲩ' => 'ⲩ',
'Ⲫ' => 'ⲫ',
'Ⲭ' => 'ⲭ',
'Ⲯ' => 'ⲯ',
'â²°' => 'â²±',
'â²²' => 'â²³',
'â²´' => 'â²µ',
'â²¶' => 'â²·',
'Ⲹ' => 'ⲹ',
'Ⲻ' => 'ⲻ',
'â²¼' => 'â²½',
'Ⲿ' => 'ⲿ',
'â³€' => 'â³<C3A2>',
'Ⳃ' => 'ⳃ',
'Ⳅ' => 'ⳅ',
'Ⳇ' => 'ⳇ',
'Ⳉ' => 'ⳉ',
'Ⳋ' => 'ⳋ',
'Ⳍ' => 'â³<C3A2>',
'Ⳏ' => 'â³<C3A2>',
'â³<C3A2>' => 'ⳑ',
'Ⳓ' => 'ⳓ',
'Ⳕ' => 'ⳕ',
'â³–' => 'â³—',
'Ⳙ' => 'ⳙ',
'Ⳛ' => 'ⳛ',
'Ⳝ' => 'â³<C3A2>',
'Ⳟ' => 'ⳟ',
'Ⳡ' => 'ⳡ',
'â³¢' => 'â³£',
'Ⳬ' => 'ⳬ',
'â³­' => 'â³®',
'â³²' => 'â³³',
'Ꙁ' => 'ê™<C3AA>',
'Ꙃ' => 'ꙃ',
'Ꙅ' => 'ꙅ',
'Ꙇ' => 'ꙇ',
'Ꙉ' => 'ꙉ',
'Ꙋ' => 'ꙋ',
'Ꙍ' => 'ê™<C3AA>',
'Ꙏ' => 'ê™<C3AA>',
'ê™<C3AA>' => 'ꙑ',
'Ꙓ' => 'ꙓ',
'Ꙕ' => 'ꙕ',
'ê™–' => 'ê™—',
'Ꙙ' => 'ꙙ',
'Ꙛ' => 'ꙛ',
'Ꙝ' => 'ê™<C3AA>',
'Ꙟ' => 'ꙟ',
'Ꙡ' => 'ꙡ',
'Ꙣ' => 'ꙣ',
'Ꙥ' => 'ꙥ',
'Ꙧ' => 'ꙧ',
'Ꙩ' => 'ꙩ',
'Ꙫ' => 'ꙫ',
'Ꙭ' => 'ꙭ',
'Ꚁ' => 'êš<C3AA>',
'Ꚃ' => 'ꚃ',
'êš„' => 'êš…',
'Ꚇ' => 'ꚇ',
'Ꚉ' => 'ꚉ',
'Ꚋ' => 'ꚋ',
'Ꚍ' => 'êš<C3AA>',
'Ꚏ' => 'êš<C3AA>',
'êš<C3AA>' => 'êš‘',
'êš’' => 'êš“',
'êš”' => 'êš•',
'êš–' => 'êš—',
'Ꚙ' => 'ꚙ',
'êšš' => 'êš›',
'Ꜣ' => 'ꜣ',
'Ꜥ' => 'ꜥ',
'Ꜧ' => 'ꜧ',
'Ꜩ' => 'ꜩ',
'Ꜫ' => 'ꜫ',
'Ꜭ' => 'ꜭ',
'Ꜯ' => 'ꜯ',
'Ꜳ' => 'ꜳ',
'Ꜵ' => 'ꜵ',
'Ꜷ' => 'ꜷ',
'Ꜹ' => 'ꜹ',
'Ꜻ' => 'ꜻ',
'Ꜽ' => 'ꜽ',
'Ꜿ' => 'ꜿ',
<>€' => 'ê<><C3AA>',
<>' => 'ê<>ƒ',
<>„' => 'ê<>…',
<>†' => 'ê<>‡',
<>ˆ' => 'ê<>‰',
<>Š' => 'ê<>',
<>Œ' => 'ê<><C3AA>',
<>Ž' => 'ê<><C3AA>',
<><C3AA>' => 'ê<>',
<>' => 'ê<>“',
<>”' => 'ê<>•',
<>' => 'ê<>—',
<>˜' => 'ê<>™',
<>š' => 'ê<>',
<>œ' => 'ê<><C3AA>',
<>ž' => 'ê<>Ÿ',
<> ' => 'ê<>¡',
<>¢' => 'ê<>£',
<>¤' => 'ê<>¥',
<>¦' => 'ê<>§',
<>¨' => 'ê<>©',
<>ª' => 'ê<>«',
<>¬' => 'ê<>­',
<>®' => 'ê<>¯',
<>¹' => 'ê<>º',
<>»' => 'ê<>¼',
<>½' => 'áµ¹',
<>¾' => 'ê<>¿',
'Ꞁ' => 'êž<C3AA>',
'Ꞃ' => 'ꞃ',
'êž„' => 'êž…',
'Ꞇ' => 'ꞇ',
'Ꞌ' => 'ꞌ',
'êž<C3AA>' => 'É¥',
'êž<C3AA>' => 'êž‘',
'êž’' => 'êž“',
'êž–' => 'êž—',
'Ꞙ' => 'ꞙ',
'êžš' => 'êž›',
'êžœ' => 'êž<C3AA>',
'Ꞟ' => 'ꞟ',
'êž ' => 'êž¡',
'Ꞣ' => 'ꞣ',
'Ꞥ' => 'ꞥ',
'Ꞧ' => 'ꞧ',
'Ꞩ' => 'ꞩ',
'Ɦ' => 'ɦ',
'Ɜ' => 'ɜ',
'Ɡ' => 'ɡ',
'Ɬ' => 'ɬ',
'êž°' => 'Êž',
'Ʇ' => 'ʇ',
'A' => 'ï½<C3AF>',
'B' => 'b',
'C' => 'c',
'D' => 'd',
'ï¼¥' => 'ï½…',
'F' => 'f',
'G' => 'g',
'H' => 'h',
'I' => 'i',
'J' => 'j',
'K' => 'k',
'L' => 'l',
'ï¼­' => 'ï½<C3AF>',
'N' => 'n',
'O' => 'ï½<C3AF>',
'ï¼°' => 'ï½<C3AF>',
'Q' => 'q',
'ï¼²' => 'ï½’',
'S' => 's',
'ï¼´' => 'ï½”',
'U' => 'u',
'ï¼¶' => 'ï½–',
'ï¼·' => 'ï½—',
'X' => 'x',
'ï¼¹' => 'ï½™',
'Z' => 'z',
<><C3B0>€' => 'ð<><C3B0>¨',
<><C3B0><EFBFBD>' => 'ð<><C3B0>©',
<><C3B0>' => 'ð<><C3B0>ª',
<><C3B0>ƒ' => 'ð<><C3B0>«',
<><C3B0>„' => 'ð<><C3B0>¬',
<><C3B0>…' => 'ð<><C3B0>­',
<><C3B0>†' => 'ð<><C3B0>®',
<><C3B0>‡' => 'ð<><C3B0>¯',
<><C3B0>ˆ' => 'ð<><C3B0>°',
<><C3B0>‰' => 'ð<><C3B0>±',
<><C3B0>Š' => 'ð<><C3B0>²',
<><C3B0>' => 'ð<><C3B0>³',
<><C3B0>Œ' => 'ð<><C3B0>´',
<><C3B0><EFBFBD>' => 'ð<><C3B0>µ',
<><C3B0>Ž' => 'ð<><C3B0>¶',
<><C3B0><EFBFBD>' => 'ð<><C3B0>·',
<><C3B0><EFBFBD>' => 'ð<><C3B0>¸',
<><C3B0>' => 'ð<><C3B0>¹',
<><C3B0>' => 'ð<><C3B0>º',
<><C3B0>“' => 'ð<><C3B0>»',
<><C3B0>”' => 'ð<><C3B0>¼',
<><C3B0>•' => 'ð<><C3B0>½',
<><C3B0>' => 'ð<><C3B0>¾',
<><C3B0>—' => 'ð<><C3B0>¿',
<><C3B0>˜' => 'ð<>€',
<><C3B0>™' => 'ð<><EFBFBD>',
<><C3B0>š' => 'ð<>',
<><C3B0>' => 'ð<>ƒ',
<><C3B0>œ' => 'ð<>„',
<><C3B0><EFBFBD>' => 'ð<>…',
<><C3B0>ž' => 'ð<>†',
<><C3B0>Ÿ' => 'ð<>‡',
<><C3B0> ' => 'ð<>ˆ',
<><C3B0>¡' => 'ð<>‰',
<><C3B0>¢' => 'ð<>Š',
<><C3B0>£' => 'ð<>',
<><C3B0>¤' => 'ð<>Œ',
<><C3B0>¥' => 'ð<><EFBFBD>',
<><C3B0>¦' => 'ð<>Ž',
<><C3B0>§' => 'ð<><EFBFBD>',
'ð‘¢ ' => 'ð‘£€',
'𑢡' => 'ð£<E28098>',
'𑢢' => '𑣂',
'𑢣' => '𑣃',
'𑢤' => '𑣄',
'ð‘¢¥' => 'ð‘£…',
'𑢦' => '𑣆',
'𑢧' => '𑣇',
'𑢨' => '𑣈',
'𑢩' => '𑣉',
'𑢪' => '𑣊',
'𑢫' => '𑣋',
'𑢬' => '𑣌',
'ð‘¢­' => 'ð£<E28098>',
'𑢮' => '𑣎',
'𑢯' => 'ð£<E28098>',
'ð‘¢°' => 'ð£<E28098>',
'𑢱' => '𑣑',
'ð‘¢²' => 'ð‘£’',
'𑢳' => '𑣓',
'ð‘¢´' => 'ð‘£”',
'𑢵' => '𑣕',
'ð‘¢¶' => 'ð‘£–',
'ð‘¢·' => 'ð‘£—',
'𑢸' => '𑣘',
'ð‘¢¹' => 'ð‘£™',
'𑢺' => '𑣚',
'ð‘¢»' => 'ð‘£›',
'𑢼' => '𑣜',
'ð‘¢½' => 'ð£<E28098>',
'𑢾' => '𑣞',
'𑢿' => '𑣟',
);
$result =& $data;
unset($data);
return $result;
<?php
static $data = array (
'a' => 'A',
'b' => 'B',
'c' => 'C',
'd' => 'D',
'e' => 'E',
'f' => 'F',
'g' => 'G',
'h' => 'H',
'i' => 'I',
'j' => 'J',
'k' => 'K',
'l' => 'L',
'm' => 'M',
'n' => 'N',
'o' => 'O',
'p' => 'P',
'q' => 'Q',
'r' => 'R',
's' => 'S',
't' => 'T',
'u' => 'U',
'v' => 'V',
'w' => 'W',
'x' => 'X',
'y' => 'Y',
'z' => 'Z',
'µ' => 'Μ',
'à' => 'À',
'á' => 'Ã<>',
'â' => 'Â',
'ã' => 'Ã',
'ä' => 'Ä',
'Ã¥' => 'Ã…',
'æ' => 'Æ',
'ç' => 'Ç',
'è' => 'È',
'é' => 'É',
'ê' => 'Ê',
'ë' => 'Ë',
'ì' => 'Ì',
'í' => 'Ã<>',
'î' => 'Î',
'ï' => 'Ã<>',
'ð' => 'Ã<>',
'ñ' => 'Ñ',
'ò' => 'Ò',
'ó' => 'Ó',
'ô' => 'Ô',
'õ' => 'Õ',
'ö' => 'Ö',
'ø' => 'Ø',
'ù' => 'Ù',
'ú' => 'Ú',
'û' => 'Û',
'ü' => 'Ü',
'ý' => 'Ã<>',
'þ' => 'Þ',
'ÿ' => 'Ÿ',
<>' => 'Ä€',
'ă' => 'Ă',
'Ä…' => 'Ä„',
'ć' => 'Ć',
'ĉ' => 'Ĉ',
'Ä‹' => 'ÄŠ',
<>' => 'Č',
<>' => 'ÄŽ',
'Ä‘' => 'Ä<>',
'Ä“' => 'Ä’',
'Ä•' => 'Ä”',
'Ä—' => 'Ä–',
'ę' => 'Ę',
'Ä›' => 'Äš',
<>' => 'Ĝ',
'ÄŸ' => 'Äž',
'Ä¡' => 'Ä ',
'Ä£' => 'Ä¢',
'ĥ' => 'Ĥ',
'ħ' => 'Ħ',
'ĩ' => 'Ĩ',
'ī' => 'Ī',
'ĭ' => 'Ĭ',
'į' => 'Į',
'ı' => 'I',
'ij' => 'IJ',
'ĵ' => 'Ĵ',
'ķ' => 'Ķ',
'ĺ' => 'Ĺ',
'ļ' => 'Ļ',
'ľ' => 'Ľ',
'Å€' => 'Ä¿',
'Å‚' => 'Å<>',
'ń' => 'Ń',
'ņ' => 'Ņ',
'ň' => 'Ň',
'Å‹' => 'ÅŠ',
<>' => 'Ō',
<>' => 'ÅŽ',
'Å‘' => 'Å<>',
'Å“' => 'Å’',
'Å•' => 'Å”',
'Å—' => 'Å–',
'ř' => 'Ř',
'Å›' => 'Åš',
<>' => 'Ŝ',
'ÅŸ' => 'Åž',
'Å¡' => 'Å ',
'Å£' => 'Å¢',
'ť' => 'Ť',
'ŧ' => 'Ŧ',
'ũ' => 'Ũ',
'ū' => 'Ū',
'ŭ' => 'Ŭ',
'ů' => 'Ů',
'ű' => 'Ű',
'ų' => 'Ų',
'ŵ' => 'Ŵ',
'ŷ' => 'Ŷ',
'ź' => 'Ź',
'ż' => 'Ż',
'ž' => 'Ž',
'Å¿' => 'S',
'ƀ' => 'Ƀ',
'ƃ' => 'Ƃ',
'Æ…' => 'Æ„',
'ƈ' => 'Ƈ',
'ƌ' => 'Ƌ',
'Æ’' => 'Æ‘',
'ƕ' => 'Ƕ',
'ƙ' => 'Ƙ',
'ƚ' => 'Ƚ',
'Æž' => 'È ',
'Æ¡' => 'Æ ',
'Æ£' => 'Æ¢',
'ƥ' => 'Ƥ',
'ƨ' => 'Ƨ',
'ƭ' => 'Ƭ',
'ư' => 'Ư',
'ƴ' => 'Ƴ',
'ƶ' => 'Ƶ',
'ƹ' => 'Ƹ',
'ƽ' => 'Ƽ',
'Æ¿' => 'Ç·',
'Ç…' => 'Ç„',
'dž' => 'DŽ',
'Lj' => 'LJ',
'lj' => 'LJ',
'Ç‹' => 'ÇŠ',
'nj' => 'NJ',
'ÇŽ' => 'Ç<>',
<>' => 'Ç<>',
'Ç’' => 'Ç‘',
'Ç”' => 'Ç“',
'Ç–' => 'Ç•',
'ǘ' => 'Ǘ',
'Çš' => 'Ç™',
'ǜ' => 'Ǜ',
<>' => 'ÆŽ',
'ÇŸ' => 'Çž',
'Ç¡' => 'Ç ',
'Ç£' => 'Ç¢',
'ǥ' => 'Ǥ',
'ǧ' => 'Ǧ',
'ǩ' => 'Ǩ',
'ǫ' => 'Ǫ',
'ǭ' => 'Ǭ',
'ǯ' => 'Ǯ',
'Dz' => 'DZ',
'dz' => 'DZ',
'ǵ' => 'Ǵ',
'ǹ' => 'Ǹ',
'ǻ' => 'Ǻ',
'ǽ' => 'Ǽ',
'ǿ' => 'Ǿ',
<>' => 'È€',
'ȃ' => 'Ȃ',
'È…' => 'È„',
'ȇ' => 'Ȇ',
'ȉ' => 'Ȉ',
'È‹' => 'ÈŠ',
<>' => 'Ȍ',
<>' => 'ÈŽ',
'È‘' => 'È<>',
'È“' => 'È’',
'È•' => 'È”',
'È—' => 'È–',
'ș' => 'Ș',
'È›' => 'Èš',
<>' => 'Ȝ',
'ÈŸ' => 'Èž',
'È£' => 'È¢',
'ȥ' => 'Ȥ',
'ȧ' => 'Ȧ',
'ȩ' => 'Ȩ',
'ȫ' => 'Ȫ',
'ȭ' => 'Ȭ',
'ȯ' => 'Ȯ',
'ȱ' => 'Ȱ',
'ȳ' => 'Ȳ',
'ȼ' => 'Ȼ',
'È¿' => 'â±¾',
'ɀ' => 'Ɀ',
'É‚' => 'É<>',
'ɇ' => 'Ɇ',
'ɉ' => 'Ɉ',
'É‹' => 'ÉŠ',
<>' => 'Ɍ',
<>' => 'ÉŽ',
<>' => 'Ɐ',
'É‘' => 'â±­',
'É’' => 'â±°',
'É“' => 'Æ<>',
'ɔ' => 'Ɔ',
'ɖ' => 'Ɖ',
'É—' => 'ÆŠ',
'É™' => 'Æ<>',
'É›' => 'Æ<>',
'ɜ' => 'Ɜ',
'É ' => 'Æ“',
'ɡ' => 'Ɡ',
'É£' => 'Æ”',
'É¥' => 'êž<C3AA>',
'ɦ' => 'Ɦ',
'ɨ' => 'Ɨ',
'É©' => 'Æ–',
'É«' => 'â±¢',
'ɬ' => 'Ɬ',
'ɯ' => 'Ɯ',
'ɱ' => 'Ɱ',
'ɲ' => 'Æ<>',
'ɵ' => 'Ɵ',
'ɽ' => 'Ɽ',
'ʀ' => 'Ʀ',
'ʃ' => 'Ʃ',
'ʇ' => 'Ʇ',
'ʈ' => 'Ʈ',
'ʉ' => 'Ʉ',
'ʊ' => 'Ʊ',
'ʋ' => 'Ʋ',
'ʌ' => 'Ʌ',
'Ê’' => 'Æ·',
'Êž' => 'êž°',
'ͅ' => 'Ι',
'ͱ' => 'Ͱ',
'ͳ' => 'Ͳ',
'ͷ' => 'Ͷ',
'ͻ' => 'Ͻ',
'ͼ' => 'Ͼ',
'ͽ' => 'Ͽ',
'ά' => 'Ά',
'έ' => 'Έ',
'ή' => 'Ή',
'ί' => 'Ί',
'α' => 'Α',
'β' => 'Β',
'γ' => 'Γ',
'δ' => 'Δ',
'ε' => 'Ε',
'ζ' => 'Ζ',
'η' => 'Η',
'θ' => 'Θ',
'ι' => 'Ι',
'κ' => 'Κ',
'λ' => 'Λ',
'μ' => 'Μ',
'ν' => 'Î<>',
'ξ' => 'Ξ',
'ο' => 'Ο',
'π' => 'Π',
<>' => 'Ρ',
'ς' => 'Σ',
'σ' => 'Σ',
'τ' => 'Τ',
'Ï…' => 'Î¥',
'φ' => 'Φ',
'χ' => 'Χ',
'ψ' => 'Ψ',
'ω' => 'Ω',
'ϊ' => 'Ϊ',
'ϋ' => 'Ϋ',
'ό' => 'Ό',
<>' => 'ÎŽ',
'ÏŽ' => 'Î<>',
<>' => 'Î’',
'ϑ' => 'Θ',
'ϕ' => 'Φ',
'ϖ' => 'Π',
'Ï—' => 'Ï<>',
'ϙ' => 'Ϙ',
'Ï›' => 'Ïš',
<>' => 'Ϝ',
'ÏŸ' => 'Ïž',
'Ï¡' => 'Ï ',
'Ï£' => 'Ï¢',
'ϥ' => 'Ϥ',
'ϧ' => 'Ϧ',
'ϩ' => 'Ϩ',
'ϫ' => 'Ϫ',
'ϭ' => 'Ϭ',
'ϯ' => 'Ϯ',
'ϰ' => 'Κ',
'ϱ' => 'Ρ',
'ϲ' => 'Ϲ',
'ϳ' => 'Ϳ',
'ϵ' => 'Ε',
'ϸ' => 'Ϸ',
'ϻ' => 'Ϻ',
'а' => 'Ð<>',
'б' => 'Б',
'в' => 'В',
'г' => 'Г',
'д' => 'Д',
'е' => 'Е',
'ж' => 'Ж',
'з' => 'З',
'и' => 'И',
'й' => 'Й',
'к' => 'К',
'л' => 'Л',
'м' => 'М',
'н' => 'Ð<>',
'о' => 'О',
'п' => 'П',
'р' => 'Р',
<>' => 'С',
'т' => 'Т',
'у' => 'У',
'ф' => 'Ф',
'Ñ…' => 'Ð¥',
'ц' => 'Ц',
'ч' => 'Ч',
'ш' => 'Ш',
'щ' => 'Щ',
'ъ' => 'Ъ',
'ы' => 'Ы',
'ь' => 'Ь',
<>' => 'Э',
'ю' => 'Ю',
<>' => 'Я',
<>' => 'Ѐ',
'Ñ‘' => 'Ð<>',
'ђ' => 'Ђ',
'ѓ' => 'Ѓ',
'є' => 'Є',
'Ñ•' => 'Ð…',
'і' => 'І',
'ї' => 'Ї',
'ј' => 'Ј',
'љ' => 'Љ',
'њ' => 'Њ',
'ћ' => 'Ћ',
'ќ' => 'Ќ',
<>' => 'Ð<>',
'Ñž' => 'ÐŽ',
'ÑŸ' => 'Ð<>',
'Ñ¡' => 'Ñ ',
'Ñ£' => 'Ñ¢',
'ѥ' => 'Ѥ',
'ѧ' => 'Ѧ',
'ѩ' => 'Ѩ',
'ѫ' => 'Ѫ',
'ѭ' => 'Ѭ',
'ѯ' => 'Ѯ',
'ѱ' => 'Ѱ',
'ѳ' => 'Ѳ',
'ѵ' => 'Ѵ',
'ѷ' => 'Ѷ',
'ѹ' => 'Ѹ',
'ѻ' => 'Ѻ',
'ѽ' => 'Ѽ',
'ѿ' => 'Ѿ',
<>' => 'Ò€',
'Ò‹' => 'ÒŠ',
<>' => 'Ҍ',
<>' => 'ÒŽ',
'Ò‘' => 'Ò<>',
'Ò“' => 'Ò’',
'Ò•' => 'Ò”',
'Ò—' => 'Ò–',
'Ò™' => 'Ò˜',
'Ò›' => 'Òš',
<>' => 'Ҝ',
'ÒŸ' => 'Òž',
'Ò¡' => 'Ò ',
'Ò£' => 'Ò¢',
'Ò¥' => 'Ò¤',
'Ò§' => 'Ò¦',
'Ò©' => 'Ò¨',
'Ò«' => 'Òª',
'Ò­' => 'Ò¬',
'Ò¯' => 'Ò®',
'Ò±' => 'Ò°',
'Ò³' => 'Ò²',
'Òµ' => 'Ò´',
'Ò·' => 'Ò¶',
'Ò¹' => 'Ò¸',
'Ò»' => 'Òº',
'Ò½' => 'Ò¼',
'Ò¿' => 'Ò¾',
'Ó‚' => 'Ó<>',
'Ó„' => 'Óƒ',
'Ó†' => 'Ó…',
'Óˆ' => 'Ó‡',
'ÓŠ' => 'Ó‰',
'ӌ' => 'Ӌ',
'ÓŽ' => 'Ó<>',
<>' => 'Ó€',
'Ó‘' => 'Ó<>',
'Ó“' => 'Ó’',
'Ó•' => 'Ó”',
'Ó—' => 'Ó–',
'Ó™' => 'Ó˜',
'Ó›' => 'Óš',
<>' => 'Ӝ',
'ÓŸ' => 'Óž',
'Ó¡' => 'Ó ',
'Ó£' => 'Ó¢',
'Ó¥' => 'Ó¤',
'Ó§' => 'Ó¦',
'Ó©' => 'Ó¨',
'Ó«' => 'Óª',
'Ó­' => 'Ó¬',
'Ó¯' => 'Ó®',
'Ó±' => 'Ó°',
'Ó³' => 'Ó²',
'Óµ' => 'Ó´',
'Ó·' => 'Ó¶',
'Ó¹' => 'Ó¸',
'Ó»' => 'Óº',
'Ó½' => 'Ó¼',
'Ó¿' => 'Ó¾',
<>' => 'Ô€',
'Ôƒ' => 'Ô‚',
'Ô…' => 'Ô„',
'Ô‡' => 'Ô†',
'Ô‰' => 'Ôˆ',
'Ô‹' => 'ÔŠ',
<>' => 'Ԍ',
<>' => 'ÔŽ',
'Ô‘' => 'Ô<>',
'Ô“' => 'Ô’',
'Ô•' => 'Ô”',
'Ô—' => 'Ô–',
'Ô™' => 'Ô˜',
'Ô›' => 'Ôš',
<>' => 'Ԝ',
'ÔŸ' => 'Ôž',
'Ô¡' => 'Ô ',
'Ô£' => 'Ô¢',
'Ô¥' => 'Ô¤',
'Ô§' => 'Ô¦',
'Ô©' => 'Ô¨',
'Ô«' => 'Ôª',
'Ô­' => 'Ô¬',
'Ô¯' => 'Ô®',
'Õ¡' => 'Ô±',
'Õ¢' => 'Ô²',
'Õ£' => 'Ô³',
'Õ¤' => 'Ô´',
'Õ¥' => 'Ôµ',
'Õ¦' => 'Ô¶',
'Õ§' => 'Ô·',
'Õ¨' => 'Ô¸',
'Õ©' => 'Ô¹',
'Õª' => 'Ôº',
'Õ«' => 'Ô»',
'Õ¬' => 'Ô¼',
'Õ­' => 'Ô½',
'Õ®' => 'Ô¾',
'Õ¯' => 'Ô¿',
'Õ°' => 'Õ€',
'Õ±' => 'Õ<>',
'Õ²' => 'Õ‚',
'Õ³' => 'Õƒ',
'Õ´' => 'Õ„',
'Õµ' => 'Õ…',
'Õ¶' => 'Õ†',
'Õ·' => 'Õ‡',
'Õ¸' => 'Õˆ',
'Õ¹' => 'Õ‰',
'Õº' => 'ÕŠ',
'Õ»' => 'Õ‹',
'ռ' => 'Ռ',
'Õ½' => 'Õ<>',
'Õ¾' => 'ÕŽ',
'Õ¿' => 'Õ<>',
'Ö€' => 'Õ<>',
<>' => 'Õ‘',
'Ö‚' => 'Õ’',
'Öƒ' => 'Õ“',
'Ö„' => 'Õ”',
'Ö…' => 'Õ•',
'Ö†' => 'Õ–',
'áµ¹' => 'ê<>½',
'áµ½' => 'â±£',
¸<C3A1>' => 'Ḁ',
'ḃ' => 'Ḃ',
'ḅ' => 'Ḅ',
'ḇ' => 'Ḇ',
'ḉ' => 'Ḉ',
'ḋ' => 'Ḋ',
¸<C3A1>' => 'Ḍ',
¸<C3A1>' => 'Ḏ',
'ḑ' => 'á¸<C3A1>',
'ḓ' => 'Ḓ',
'ḕ' => 'Ḕ',
'ḗ' => 'Ḗ',
'ḙ' => 'Ḙ',
'ḛ' => 'Ḛ',
¸<C3A1>' => 'Ḝ',
'ḟ' => 'Ḟ',
'ḡ' => 'Ḡ',
'ḣ' => 'Ḣ',
'ḥ' => 'Ḥ',
'ḧ' => 'Ḧ',
'ḩ' => 'Ḩ',
'ḫ' => 'Ḫ',
'ḭ' => 'Ḭ',
'ḯ' => 'Ḯ',
'ḱ' => 'Ḱ',
'ḳ' => 'Ḳ',
'ḵ' => 'Ḵ',
'ḷ' => 'Ḷ',
'ḹ' => 'Ḹ',
'ḻ' => 'Ḻ',
'ḽ' => 'Ḽ',
'ḿ' => 'Ḿ',
'á¹<C3A1>' => 'á¹€',
'ṃ' => 'Ṃ',
'ṅ' => 'Ṅ',
'ṇ' => 'Ṇ',
'ṉ' => 'Ṉ',
'ṋ' => 'Ṋ',
'á¹<C3A1>' => 'Ṍ',
'á¹<C3A1>' => 'Ṏ',
'ṑ' => 'á¹<C3A1>',
'ṓ' => 'Ṓ',
'ṕ' => 'Ṕ',
'á¹—' => 'á¹–',
'ṙ' => 'Ṙ',
'ṛ' => 'Ṛ',
'á¹<C3A1>' => 'Ṝ',
'ṟ' => 'Ṟ',
'ṡ' => 'Ṡ',
'á¹£' => 'á¹¢',
'ṥ' => 'Ṥ',
'ṧ' => 'Ṧ',
'ṩ' => 'Ṩ',
'ṫ' => 'Ṫ',
'ṭ' => 'Ṭ',
'ṯ' => 'Ṯ',
'á¹±' => 'á¹°',
'á¹³' => 'á¹²',
'á¹µ' => 'á¹´',
'á¹·' => 'á¹¶',
'ṹ' => 'Ṹ',
'ṻ' => 'Ṻ',
'á¹½' => 'á¹¼',
'ṿ' => 'Ṿ',
'áº<C3A1>' => 'Ẁ',
'ẃ' => 'Ẃ',
'ẅ' => 'Ẅ',
'ẇ' => 'Ẇ',
'ẉ' => 'Ẉ',
'ẋ' => 'Ẋ',
'áº<C3A1>' => 'Ẍ',
'áº<C3A1>' => 'Ẏ',
'ẑ' => 'áº<C3A1>',
'ẓ' => 'Ẓ',
'ẕ' => 'Ẕ',
'ẛ' => 'Ṡ',
'ạ' => 'Ạ',
'ả' => 'Ả',
'ấ' => 'Ấ',
'ầ' => 'Ầ',
'ẩ' => 'Ẩ',
'ẫ' => 'Ẫ',
'ậ' => 'Ậ',
'ắ' => 'Ắ',
'ằ' => 'Ằ',
'ẳ' => 'Ẳ',
'ẵ' => 'Ẵ',
'ặ' => 'Ặ',
'ẹ' => 'Ẹ',
'ẻ' => 'Ẻ',
'ẽ' => 'Ẽ',
'ế' => 'Ế',
'á»<C3A1>' => 'Ề',
'ể' => 'Ể',
'ễ' => 'Ễ',
'ệ' => 'Ệ',
'ỉ' => 'Ỉ',
'ị' => 'Ị',
'á»<C3A1>' => 'Ọ',
'á»<C3A1>' => 'Ỏ',
'ố' => 'á»<C3A1>',
'ồ' => 'Ồ',
'ổ' => 'Ổ',
'á»—' => 'á»–',
'ộ' => 'Ộ',
'ớ' => 'Ớ',
'á»<C3A1>' => 'Ờ',
'ở' => 'Ở',
'ỡ' => 'Ỡ',
'ợ' => 'Ợ',
'ụ' => 'Ụ',
'ủ' => 'Ủ',
'ứ' => 'Ứ',
'ừ' => 'Ừ',
'ử' => 'Ử',
'ữ' => 'Ữ',
'á»±' => 'á»°',
'ỳ' => 'Ỳ',
'ỵ' => 'Ỵ',
'á»·' => 'á»¶',
'ỹ' => 'Ỹ',
'ỻ' => 'Ỻ',
'ỽ' => 'Ỽ',
'ỿ' => 'Ỿ',
'ἀ' => 'Ἀ',
'á¼<C3A1>' => 'Ἁ',
'ἂ' => 'Ἂ',
'ἃ' => 'Ἃ',
'ἄ' => 'Ἄ',
'á¼…' => 'á¼<C3A1>',
'ἆ' => 'Ἆ',
'ἇ' => 'á¼<C3A1>',
'á¼<C3A1>' => 'Ἐ',
'ἑ' => 'Ἑ',
'ἒ' => 'Ἒ',
'ἓ' => 'Ἓ',
'ἔ' => 'Ἔ',
'ἕ' => 'á¼<C3A1>',
'ἠ' => 'Ἠ',
'ἡ' => 'Ἡ',
'ἢ' => 'Ἢ',
'ἣ' => 'Ἣ',
'ἤ' => 'Ἤ',
'á¼¥' => 'á¼­',
'ἦ' => 'Ἦ',
'ἧ' => 'Ἧ',
'ἰ' => 'Ἰ',
'á¼±' => 'á¼¹',
'ἲ' => 'Ἲ',
'á¼³' => 'á¼»',
'á¼´' => 'á¼¼',
'á¼µ' => 'á¼½',
'á¼¶' => 'á¼¾',
'ἷ' => 'Ἷ',
'ὀ' => 'Ὀ',
'á½<C3A1>' => 'Ὁ',
'ὂ' => 'Ὂ',
'ὃ' => 'Ὃ',
'ὄ' => 'Ὄ',
'á½…' => 'á½<C3A1>',
'ὑ' => 'Ὑ',
'ὓ' => 'Ὓ',
'ὕ' => 'á½<C3A1>',
'ὗ' => 'Ὗ',
'ὠ' => 'Ὠ',
'ὡ' => 'Ὡ',
'ὢ' => 'Ὢ',
'ὣ' => 'Ὣ',
'ὤ' => 'Ὤ',
'á½¥' => 'á½­',
'ὦ' => 'Ὦ',
'ὧ' => 'Ὧ',
'ὰ' => 'Ὰ',
'á½±' => 'á¾»',
'ὲ' => 'Ὲ',
'έ' => 'Έ',
'ὴ' => 'Ὴ',
'á½µ' => 'á¿‹',
'ὶ' => 'Ὶ',
'á½·' => 'á¿›',
'ὸ' => 'Ὸ',
'ό' => 'Ό',
'ὺ' => 'Ὺ',
'á½»' => 'á¿«',
'ὼ' => 'Ὼ',
'á½½' => 'á¿»',
'ᾀ' => 'ᾈ',
'á¾<C3A1>' => 'ᾉ',
'ᾂ' => 'ᾊ',
'ᾃ' => 'ᾋ',
'ᾄ' => 'ᾌ',
'á¾…' => 'á¾<C3A1>',
'ᾆ' => 'ᾎ',
'ᾇ' => 'á¾<C3A1>',
'á¾<C3A1>' => 'ᾘ',
'ᾑ' => 'ᾙ',
'ᾒ' => 'ᾚ',
'ᾓ' => 'ᾛ',
'ᾔ' => 'ᾜ',
'ᾕ' => 'á¾<C3A1>',
'ᾖ' => 'ᾞ',
'ᾗ' => 'ᾟ',
'ᾠ' => 'ᾨ',
'ᾡ' => 'ᾩ',
'ᾢ' => 'ᾪ',
'ᾣ' => 'ᾫ',
'ᾤ' => 'ᾬ',
'á¾¥' => 'á¾­',
'ᾦ' => 'ᾮ',
'ᾧ' => 'ᾯ',
'ᾰ' => 'Ᾰ',
'á¾±' => 'á¾¹',
'á¾³' => 'á¾¼',
'ι' => 'Ι',
'ῃ' => 'ῌ',
'á¿<C3A1>' => 'Ῐ',
'á¿‘' => 'á¿™',
'ῠ' => 'Ῠ',
'á¿¡' => 'á¿©',
'ῥ' => 'Ῥ',
'ῳ' => 'ῼ',
'ⅎ' => 'Ⅎ',
'â…°' => 'â… ',
'â…±' => 'â…¡',
'â…²' => 'â…¢',
'â…³' => 'â…£',
'â…´' => 'â…¤',
'â…µ' => 'â…¥',
'â…¶' => 'â…¦',
'â…·' => 'â…§',
'â…¸' => 'â…¨',
'â…¹' => 'â…©',
'â…º' => 'â…ª',
'â…»' => 'â…«',
'â…¼' => 'â…¬',
'â…½' => 'â…­',
'â…¾' => 'â…®',
'â…¿' => 'â…¯',
'ↄ' => 'Ↄ',
'â“<C3A2>' => 'â’¶',
'â“‘' => 'â’·',
'â“’' => 'â’¸',
'â““' => 'â’¹',
'â“”' => 'â’º',
'â“•' => 'â’»',
'â“–' => 'â’¼',
'â“—' => 'â’½',
'ⓘ' => 'Ⓘ',
'â“™' => 'â’¿',
'ⓚ' => 'Ⓚ',
'â“›' => 'â“<C3A2>',
'ⓜ' => 'Ⓜ',
'â“<C3A2>' => 'Ⓝ',
'ⓞ' => 'Ⓞ',
'ⓟ' => 'Ⓟ',
'ⓠ' => 'Ⓠ',
'ⓡ' => 'Ⓡ',
'ⓢ' => 'Ⓢ',
'ⓣ' => 'Ⓣ',
'ⓤ' => 'Ⓤ',
'â“¥' => 'â“‹',
'ⓦ' => 'Ⓦ',
'â“§' => 'â“<C3A2>',
'ⓨ' => 'Ⓨ',
'â“©' => 'â“<C3A2>',
'â°°' => 'â°€',
'â°±' => 'â°<C3A2>',
'â°²' => 'â°‚',
'â°³' => 'â°ƒ',
'â°´' => 'â°„',
'â°µ' => 'â°…',
'â°¶' => 'â°†',
'â°·' => 'â°‡',
'â°¸' => 'â°ˆ',
'â°¹' => 'â°‰',
'â°º' => 'â°Š',
'â°»' => 'â°‹',
'ⰼ' => 'Ⰼ',
'â°½' => 'â°<C3A2>',
'â°¾' => 'â°Ž',
'â°¿' => 'â°<C3A2>',
'â±€' => 'â°<C3A2>',
'â±<C3A2>' => 'â°‘',
'ⱂ' => 'Ⱂ',
'ⱃ' => 'Ⱃ',
'ⱄ' => 'Ⱄ',
'â±…' => 'â°•',
'ⱆ' => 'Ⱆ',
'ⱇ' => 'Ⱇ',
'ⱈ' => 'Ⱈ',
'ⱉ' => 'Ⱉ',
'ⱊ' => 'Ⱊ',
'ⱋ' => 'Ⱋ',
'ⱌ' => 'Ⱌ',
'â±<C3A2>' => 'â°<C3A2>',
'ⱎ' => 'Ⱎ',
'â±<C3A2>' => 'â°Ÿ',
'â±<C3A2>' => 'â° ',
'ⱑ' => 'Ⱑ',
'â±’' => 'â°¢',
'ⱓ' => 'Ⱓ',
'â±”' => 'â°¤',
'ⱕ' => 'Ⱕ',
'â±–' => 'â°¦',
'â±—' => 'â°§',
'ⱘ' => 'Ⱘ',
'â±™' => 'â°©',
'ⱚ' => 'Ⱚ',
'â±›' => 'â°«',
'ⱜ' => 'Ⱜ',
'â±<C3A2>' => 'â°­',
'ⱞ' => 'Ⱞ',
'ⱡ' => 'Ⱡ',
'ⱥ' => 'Ⱥ',
'ⱦ' => 'Ⱦ',
'ⱨ' => 'Ⱨ',
'ⱪ' => 'Ⱪ',
'ⱬ' => 'Ⱬ',
'â±³' => 'â±²',
'â±¶' => 'â±µ',
'â²<C3A2>' => 'â²€',
'ⲃ' => 'Ⲃ',
'ⲅ' => 'Ⲅ',
'ⲇ' => 'Ⲇ',
'ⲉ' => 'Ⲉ',
'ⲋ' => 'Ⲋ',
'â²<C3A2>' => 'Ⲍ',
'â²<C3A2>' => 'Ⲏ',
'ⲑ' => 'â²<C3A2>',
'ⲓ' => 'Ⲓ',
'ⲕ' => 'Ⲕ',
'â²—' => 'â²–',
'ⲙ' => 'Ⲙ',
'ⲛ' => 'Ⲛ',
'â²<C3A2>' => 'Ⲝ',
'ⲟ' => 'Ⲟ',
'ⲡ' => 'Ⲡ',
'â²£' => 'â²¢',
'ⲥ' => 'Ⲥ',
'ⲧ' => 'Ⲧ',
'ⲩ' => 'Ⲩ',
'ⲫ' => 'Ⲫ',
'ⲭ' => 'Ⲭ',
'ⲯ' => 'Ⲯ',
'â²±' => 'â²°',
'â²³' => 'â²²',
'â²µ' => 'â²´',
'â²·' => 'â²¶',
'ⲹ' => 'Ⲹ',
'ⲻ' => 'Ⲻ',
'â²½' => 'â²¼',
'ⲿ' => 'Ⲿ',
'â³<C3A2>' => 'â³€',
'ⳃ' => 'Ⳃ',
'ⳅ' => 'Ⳅ',
'ⳇ' => 'Ⳇ',
'ⳉ' => 'Ⳉ',
'ⳋ' => 'Ⳋ',
'â³<C3A2>' => 'Ⳍ',
'â³<C3A2>' => 'Ⳏ',
'ⳑ' => 'â³<C3A2>',
'ⳓ' => 'Ⳓ',
'ⳕ' => 'Ⳕ',
'â³—' => 'â³–',
'ⳙ' => 'Ⳙ',
'ⳛ' => 'Ⳛ',
'â³<C3A2>' => 'Ⳝ',
'ⳟ' => 'Ⳟ',
'ⳡ' => 'Ⳡ',
'â³£' => 'â³¢',
'ⳬ' => 'Ⳬ',
'â³®' => 'â³­',
'â³³' => 'â³²',
'â´€' => 'á‚ ',
´<C3A2>' => 'á‚¡',
'â´‚' => 'á‚¢',
'â´ƒ' => 'á‚£',
'ⴄ' => 'Ⴄ',
'â´…' => 'á‚¥',
'ⴆ' => 'Ⴆ',
'â´‡' => 'á‚§',
'ⴈ' => 'Ⴈ',
'â´‰' => 'á‚©',
'ⴊ' => 'Ⴊ',
'â´‹' => 'á‚«',
'ⴌ' => 'Ⴌ',
´<C3A2>' => 'á‚­',
'â´Ž' => 'á‚®',
´<C3A2>' => 'Ⴏ',
´<C3A2>' => 'á‚°',
'ⴑ' => 'Ⴑ',
'ⴒ' => 'Ⴒ',
'ⴓ' => 'Ⴓ',
'â´”' => 'á‚´',
'ⴕ' => 'Ⴕ',
'â´–' => 'á‚¶',
'â´—' => 'á‚·',
'ⴘ' => 'Ⴘ',
'ⴙ' => 'Ⴙ',
'ⴚ' => 'Ⴚ',
'â´›' => 'á‚»',
'ⴜ' => 'Ⴜ',
´<C3A2>' => 'Ⴝ',
'ⴞ' => 'Ⴞ',
'â´Ÿ' => 'á‚¿',
'ⴠ' => 'Ⴠ',
'â´¡' => 'áƒ<C3A1>',
'ⴢ' => 'Ⴢ',
'ⴣ' => 'Ⴣ',
'ⴤ' => 'Ⴤ',
'ⴥ' => 'Ⴥ',
'ⴧ' => 'Ⴧ',
'â´­' => 'áƒ<C3A1>',
'ê™<C3AA>' => 'Ꙁ',
'ꙃ' => 'Ꙃ',
'ꙅ' => 'Ꙅ',
'ꙇ' => 'Ꙇ',
'ꙉ' => 'Ꙉ',
'ꙋ' => 'Ꙋ',
'ê™<C3AA>' => 'Ꙍ',
'ê™<C3AA>' => 'Ꙏ',
'ꙑ' => 'ê™<C3AA>',
'ꙓ' => 'Ꙓ',
'ꙕ' => 'Ꙕ',
'ê™—' => 'ê™–',
'ꙙ' => 'Ꙙ',
'ꙛ' => 'Ꙛ',
'ê™<C3AA>' => 'Ꙝ',
'ꙟ' => 'Ꙟ',
'ꙡ' => 'Ꙡ',
'ꙣ' => 'Ꙣ',
'ꙥ' => 'Ꙥ',
'ꙧ' => 'Ꙧ',
'ꙩ' => 'Ꙩ',
'ꙫ' => 'Ꙫ',
'ꙭ' => 'Ꙭ',
'êš<C3AA>' => 'Ꚁ',
'ꚃ' => 'Ꚃ',
'êš…' => 'êš„',
'ꚇ' => 'Ꚇ',
'ꚉ' => 'Ꚉ',
'ꚋ' => 'Ꚋ',
'êš<C3AA>' => 'Ꚍ',
'êš<C3AA>' => 'Ꚏ',
'êš‘' => 'êš<C3AA>',
'êš“' => 'êš’',
'êš•' => 'êš”',
'êš—' => 'êš–',
'ꚙ' => 'Ꚙ',
'êš›' => 'êšš',
'ꜣ' => 'Ꜣ',
'ꜥ' => 'Ꜥ',
'ꜧ' => 'Ꜧ',
'ꜩ' => 'Ꜩ',
'ꜫ' => 'Ꜫ',
'ꜭ' => 'Ꜭ',
'ꜯ' => 'Ꜯ',
'ꜳ' => 'Ꜳ',
'ꜵ' => 'Ꜵ',
'ꜷ' => 'Ꜷ',
'ꜹ' => 'Ꜹ',
'ꜻ' => 'Ꜻ',
'ꜽ' => 'Ꜽ',
'ꜿ' => 'Ꜿ',
<><C3AA>' => 'ê<>€',
<>ƒ' => 'ê<>',
<>…' => 'ê<>„',
<>‡' => 'ê<>†',
<>‰' => 'ê<>ˆ',
<>' => 'ê<>Š',
<><C3AA>' => 'ê<>Œ',
<><C3AA>' => 'ê<>Ž',
<>' => 'ê<><C3AA>',
<>“' => 'ê<>',
<>•' => 'ê<>”',
<>—' => 'ê<>',
<>™' => 'ê<>˜',
<>' => 'ê<>š',
<><C3AA>' => 'ê<>œ',
<>Ÿ' => 'ê<>ž',
<>¡' => 'ê<> ',
<>£' => 'ê<>¢',
<>¥' => 'ê<>¤',
<>§' => 'ê<>¦',
<>©' => 'ê<>¨',
<>«' => 'ê<>ª',
<>­' => 'ê<>¬',
<>¯' => 'ê<>®',
<>º' => 'ê<>¹',
<>¼' => 'ê<>»',
<>¿' => 'ê<>¾',
'êž<C3AA>' => 'Ꞁ',
'ꞃ' => 'Ꞃ',
'êž…' => 'êž„',
'ꞇ' => 'Ꞇ',
'ꞌ' => 'Ꞌ',
'êž‘' => 'êž<C3AA>',
'êž“' => 'êž’',
'êž—' => 'êž–',
'ꞙ' => 'Ꞙ',
'êž›' => 'êžš',
'êž<C3AA>' => 'êžœ',
'ꞟ' => 'Ꞟ',
'êž¡' => 'êž ',
'ꞣ' => 'Ꞣ',
'ꞥ' => 'Ꞥ',
'ꞧ' => 'Ꞧ',
'ꞩ' => 'Ꞩ',
'ï½<C3AF>' => 'A',
'b' => 'B',
'c' => 'C',
'd' => 'D',
'ï½…' => 'ï¼¥',
'f' => 'F',
'g' => 'G',
'h' => 'H',
'i' => 'I',
'j' => 'J',
'k' => 'K',
'l' => 'L',
'ï½<C3AF>' => 'ï¼­',
'n' => 'N',
'ï½<C3AF>' => 'O',
'ï½<C3AF>' => 'ï¼°',
'q' => 'Q',
'ï½’' => 'ï¼²',
's' => 'S',
'ï½”' => 'ï¼´',
'u' => 'U',
'ï½–' => 'ï¼¶',
'ï½—' => 'ï¼·',
'x' => 'X',
'ï½™' => 'ï¼¹',
'z' => 'Z',
<><C3B0>¨' => 'ð<><C3B0>€',
<><C3B0>©' => 'ð<><C3B0><EFBFBD>',
<><C3B0>ª' => 'ð<><C3B0>',
<><C3B0>«' => 'ð<><C3B0>ƒ',
<><C3B0>¬' => 'ð<><C3B0>„',
<><C3B0>­' => 'ð<><C3B0>…',
<><C3B0>®' => 'ð<><C3B0>†',
<><C3B0>¯' => 'ð<><C3B0>‡',
<><C3B0>°' => 'ð<><C3B0>ˆ',
<><C3B0>±' => 'ð<><C3B0>‰',
<><C3B0>²' => 'ð<><C3B0>Š',
<><C3B0>³' => 'ð<><C3B0>',
<><C3B0>´' => 'ð<><C3B0>Œ',
<><C3B0>µ' => 'ð<><C3B0><EFBFBD>',
<><C3B0>¶' => 'ð<><C3B0>Ž',
<><C3B0>·' => 'ð<><C3B0><EFBFBD>',
<><C3B0>¸' => 'ð<><C3B0><EFBFBD>',
<><C3B0>¹' => 'ð<><C3B0>',
<><C3B0>º' => 'ð<><C3B0>',
<><C3B0>»' => 'ð<><C3B0>“',
<><C3B0>¼' => 'ð<><C3B0>”',
<><C3B0>½' => 'ð<><C3B0>•',
<><C3B0>¾' => 'ð<><C3B0>',
<><C3B0>¿' => 'ð<><C3B0>—',
<>€' => 'ð<><C3B0>˜',
<><EFBFBD>' => 'ð<><C3B0>™',
<>' => 'ð<><C3B0>š',
<>ƒ' => 'ð<><C3B0>',
<>„' => 'ð<><C3B0>œ',
<>…' => 'ð<><C3B0><EFBFBD>',
<>†' => 'ð<><C3B0>ž',
<>‡' => 'ð<><C3B0>Ÿ',
<>ˆ' => 'ð<><C3B0> ',
<>‰' => 'ð<><C3B0>¡',
<>Š' => 'ð<><C3B0>¢',
<>' => 'ð<><C3B0>£',
<>Œ' => 'ð<><C3B0>¤',
<><EFBFBD>' => 'ð<><C3B0>¥',
<>Ž' => 'ð<><C3B0>¦',
<><EFBFBD>' => 'ð<><C3B0>§',
'ð‘£€' => 'ð‘¢ ',
£<E28098>' => '𑢡',
'𑣂' => '𑢢',
'𑣃' => '𑢣',
'𑣄' => '𑢤',
'ð‘£…' => 'ð‘¢¥',
'𑣆' => '𑢦',
'𑣇' => '𑢧',
'𑣈' => '𑢨',
'𑣉' => '𑢩',
'𑣊' => '𑢪',
'𑣋' => '𑢫',
'𑣌' => '𑢬',
£<E28098>' => 'ð‘¢­',
'𑣎' => '𑢮',
£<E28098>' => '𑢯',
£<E28098>' => 'ð‘¢°',
'𑣑' => '𑢱',
'ð‘£’' => 'ð‘¢²',
'𑣓' => '𑢳',
'ð‘£”' => 'ð‘¢´',
'𑣕' => '𑢵',
'ð‘£–' => 'ð‘¢¶',
'ð‘£—' => 'ð‘¢·',
'𑣘' => '𑢸',
'ð‘£™' => 'ð‘¢¹',
'𑣚' => '𑢺',
'ð‘£›' => 'ð‘¢»',
'𑣜' => '𑢼',
£<E28098>' => 'ð‘¢½',
'𑣞' => '𑢾',
'𑣟' => '𑢿',
);
$result =& $data;
unset($data);
return $result;
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Process\Exception;
/**
* Marker Interface for the Process Component.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
interface ExceptionInterface
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Process\Exception;
/**
* InvalidArgumentException for the Process Component.
*
* @author Romain Neutron <imprec@gmail.com>
*/
class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Process\Exception;
/**
* LogicException for the Process Component.
*
* @author Romain Neutron <imprec@gmail.com>
*/
class LogicException extends \LogicException implements ExceptionInterface
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Process\Exception;
use Symfony\Component\Process\Process;
/**
* Exception for failed processes.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class ProcessFailedException extends RuntimeException
{
private $process;
public function __construct(Process $process)
{
if ($process->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;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Process\Exception;
use Symfony\Component\Process\Process;
/**
* Exception that is thrown when a process times out.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class ProcessTimedOutException extends RuntimeException
{
const TYPE_GENERAL = 1;
const TYPE_IDLE = 2;
private $process;
private $timeoutType;
public function __construct(Process $process, $timeoutType)
{
$this->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 $this->timeoutType === self::TYPE_GENERAL;
}
public function isIdleTimeout()
{
return $this->timeoutType === self::TYPE_IDLE;
}
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));
}
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Process\Exception;
/**
* RuntimeException for the Process Component.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class RuntimeException extends \RuntimeException implements ExceptionInterface
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Process;
/**
* Generic executable finder.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class ExecutableFinder
{
private $suffixes = array('.exe', '.bat', '.cmd', '.com');
/**
* Replaces default suffixes of executable.
*
* @param array $suffixes
*/
public function setSuffixes(array $suffixes)
{
$this->suffixes = $suffixes;
}
/**
* Adds new possible suffix to check for executable.
*
* @param string $suffix
*/
public function addSuffix($suffix)
{
$this->suffixes[] = $suffix;
}
/**
* Finds an executable by name.
*
* @param string $name The executable name (without the extension)
* @param string $default The default to return if no executable is found
* @param array $extraDirs Additional dirs to check into
*
* @return string The executable path or default value
*/
public function find($name, $default = null, array $extraDirs = array())
{
if (ini_get('open_basedir')) {
$searchPath = explode(PATH_SEPARATOR, ini_get('open_basedir'));
$dirs = array();
foreach ($searchPath as $path) {
// Silencing against https://bugs.php.net/69240
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 = array('');
if ('\\' === DIRECTORY_SEPARATOR) {
$pathExt = getenv('PATHEXT');
$suffixes = $pathExt ? explode(PATH_SEPARATOR, $pathExt) : $this->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;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Process;
use Symfony\Component\Process\Exception\RuntimeException;
/**
* Provides a way to continuously write to the input of a Process until the InputStream is closed.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
class InputStream implements \IteratorAggregate
{
private $onEmpty = null;
private $input = array();
private $open = true;
/**
* Sets a callback that is called when the write buffer becomes empty.
*/
public function onEmpty(callable $onEmpty = null)
{
$this->onEmpty = $onEmpty;
}
/**
* Appends an input to the write buffer.
*
* @param resource|scalar|\Traversable|null The input to append as stream resource, scalar or \Traversable
*/
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);
}
/**
* Closes the write buffer.
*/
public function close()
{
$this->open = false;
}
/**
* Tells whether the write buffer is closed or not.
*/
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));
}
}
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Process;
/**
* An executable finder specifically designed for the PHP executable.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class PhpExecutableFinder
{
private $executableFinder;
public function __construct()
{
$this->executableFinder = new ExecutableFinder();
}
/**
* Finds The PHP executable.
*
* @param bool $includeArgs Whether or not include command arguments
*
* @return string|false The PHP executable path or false if it cannot be found
*/
public function find($includeArgs = true)
{
$args = $this->findArguments();
$args = $includeArgs && $args ? ' '.implode(' ', $args) : '';
// HHVM support
if (defined('HHVM_VERSION')) {
return (getenv('PHP_BINARY') ?: PHP_BINARY).$args;
}
// PHP_BINARY return the current sapi executable
if (PHP_BINARY && in_array(PHP_SAPI, array('cli', 'cli-server', 'phpdbg')) && is_file(PHP_BINARY)) {
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;
}
}
$dirs = array(PHP_BINDIR);
if ('\\' === DIRECTORY_SEPARATOR) {
$dirs[] = 'C:\xampp\php\\';
}
return $this->executableFinder->find('php', false, $dirs);
}
/**
* Finds the PHP executable arguments.
*
* @return array The PHP executable arguments
*/
public function findArguments()
{
$arguments = array();
if (defined('HHVM_VERSION')) {
$arguments[] = '--php';
} elseif ('phpdbg' === PHP_SAPI) {
$arguments[] = '-qrr';
}
return $arguments;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Process;
use Symfony\Component\Process\Exception\RuntimeException;
/**
* PhpProcess runs a PHP script in an independent process.
*
* $p = new PhpProcess('<?php echo "foo"; ?>');
* $p->run();
* print $p->getOutput()."\n";
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class PhpProcess extends Process
{
/**
* Constructor.
*
* @param string $script The PHP script to run (as a string)
* @param string|null $cwd The working directory or null to use the working dir of the current PHP process
* @param array|null $env The environment variables or null to use the same environment as the current PHP process
* @param int $timeout The timeout in seconds
* @param array $options An array of options for proc_open
*/
public function __construct($script, $cwd = null, array $env = null, $timeout = 60, array $options = array())
{
$executableFinder = new PhpExecutableFinder();
if (false === $php = $executableFinder->find()) {
$php = null;
}
if ('phpdbg' === PHP_SAPI) {
$file = tempnam(sys_get_temp_dir(), 'dbg');
file_put_contents($file, $script);
register_shutdown_function('unlink', $file);
$php .= ' '.ProcessUtils::escapeArgument($file);
$script = null;
}
if ('\\' !== DIRECTORY_SEPARATOR && null !== $php) {
// exec is mandatory to deal with sending a signal to the process
// see https://github.com/symfony/symfony/issues/5030 about prepending
// command with exec
$php = 'exec '.$php;
}
parent::__construct($php, $cwd, $env, $script, $timeout, $options);
}
/**
* Sets the path to the PHP binary to use.
*/
public function setPhpBinary($php)
{
$this->setCommandLine($php);
}
/**
* {@inheritdoc}
*/
public function start(callable $callback = null)
{
if (null === $this->getCommandLine()) {
throw new RuntimeException('Unable to find the PHP executable.');
}
parent::start($callback);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Process\Pipes;
use Symfony\Component\Process\Exception\InvalidArgumentException;
/**
* @author Romain Neutron <imprec@gmail.com>
*
* @internal
*/
abstract class AbstractPipes implements PipesInterface
{
/** @var array */
public $pipes = array();
/** @var string */
private $inputBuffer = '';
/** @var resource|scalar|\Iterator|null */
private $input;
/** @var bool */
private $blocked = true;
public function __construct($input)
{
if (is_resource($input) || $input instanceof \Iterator) {
$this->input = $input;
} elseif (is_string($input)) {
$this->inputBuffer = $input;
} else {
$this->inputBuffer = (string) $input;
}
}
/**
* {@inheritdoc}
*/
public function close()
{
foreach ($this->pipes as $pipe) {
fclose($pipe);
}
$this->pipes = array();
}
/**
* Returns true if a system call has been interrupted.
*
* @return bool
*/
protected function hasSystemCallBeenInterrupted()
{
$lastError = error_get_last();
// stream_select returns false when the `select` system call is interrupted by an incoming signal
return isset($lastError['message']) && false !== stripos($lastError['message'], 'interrupted system call');
}
/**
* Unblocks streams.
*/
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;
}
/**
* Writes input to stdin.
*
* @throws InvalidArgumentException When an input iterator yields a non supported value
*/
protected function write()
{
if (!isset($this->pipes[0])) {
return;
}
$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 = array();
$w = array($this->pipes[0]);
// let's have a look if something changed in streams
if (false === $n = @stream_select($r, $w, $e, 0, 0)) {
return;
}
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 array($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 array($this->pipes[0]);
}
}
if (feof($input)) {
if ($this->input instanceof \Iterator) {
$this->input->next();
} else {
$this->input = null;
}
}
}
}
// no input to read on resource, buffer is empty
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 array($this->pipes[0]);
}
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Process\Pipes;
/**
* PipesInterface manages descriptors and pipes for the use of proc_open.
*
* @author Romain Neutron <imprec@gmail.com>
*
* @internal
*/
interface PipesInterface
{
const CHUNK_SIZE = 16384;
/**
* Returns an array of descriptors for the use of proc_open.
*
* @return array
*/
public function getDescriptors();
/**
* Returns an array of filenames indexed by their related stream in case these pipes use temporary files.
*
* @return string[]
*/
public function getFiles();
/**
* Reads data in file handles and pipes.
*
* @param bool $blocking Whether to use blocking calls or not
* @param bool $close Whether to close pipes if they've reached EOF
*
* @return string[] An array of read data indexed by their fd
*/
public function readAndWrite($blocking, $close = false);
/**
* Returns if the current state has open file handles or pipes.
*
* @return bool
*/
public function areOpen();
/**
* Returns if pipes are able to read output.
*
* @return bool
*/
public function haveReadSupport();
/**
* Closes file handles and pipes.
*/
public function close();
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Process\Pipes;
use Symfony\Component\Process\Process;
/**
* UnixPipes implementation uses unix pipes as handles.
*
* @author Romain Neutron <imprec@gmail.com>
*
* @internal
*/
class UnixPipes extends AbstractPipes
{
/** @var bool */
private $ttyMode;
/** @var bool */
private $ptyMode;
/** @var bool */
private $haveReadSupport;
public function __construct($ttyMode, $ptyMode, $input, $haveReadSupport)
{
$this->ttyMode = (bool) $ttyMode;
$this->ptyMode = (bool) $ptyMode;
$this->haveReadSupport = (bool) $haveReadSupport;
parent::__construct($input);
}
public function __destruct()
{
$this->close();
}
/**
* {@inheritdoc}
*/
public function getDescriptors()
{
if (!$this->haveReadSupport) {
$nullstream = fopen('/dev/null', 'c');
return array(
array('pipe', 'r'),
$nullstream,
$nullstream,
);
}
if ($this->ttyMode) {
return array(
array('file', '/dev/tty', 'r'),
array('file', '/dev/tty', 'w'),
array('file', '/dev/tty', 'w'),
);
}
if ($this->ptyMode && Process::isPtySupported()) {
return array(
array('pty'),
array('pty'),
array('pty'),
);
}
return array(
array('pipe', 'r'),
array('pipe', 'w'), // stdout
array('pipe', 'w'), // stderr
);
}
/**
* {@inheritdoc}
*/
public function getFiles()
{
return array();
}
/**
* {@inheritdoc}
*/
public function readAndWrite($blocking, $close = false)
{
$this->unblock();
$w = $this->write();
$read = $e = array();
$r = $this->pipes;
unset($r[0]);
// let's have a look if something changed in streams
if (($r || $w) && false === $n = @stream_select($r, $w, $e, 0, $blocking ? Process::TIMEOUT_PRECISION * 1E6 : 0)) {
// if a system call has been interrupted, forget about it, let's try again
// otherwise, an error occurred, let's reset pipes
if (!$this->hasSystemCallBeenInterrupted()) {
$this->pipes = array();
}
return $read;
}
foreach ($r as $pipe) {
// prior PHP 5.4 the array passed to stream_select is modified and
// lose key association, we have to find back the key
$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;
}
/**
* {@inheritdoc}
*/
public function haveReadSupport()
{
return $this->haveReadSupport;
}
/**
* {@inheritdoc}
*/
public function areOpen()
{
return (bool) $this->pipes;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Process\Pipes;
use Symfony\Component\Process\Process;
use Symfony\Component\Process\Exception\RuntimeException;
/**
* WindowsPipes implementation uses temporary files as handles.
*
* @see https://bugs.php.net/bug.php?id=51800
* @see https://bugs.php.net/bug.php?id=65650
*
* @author Romain Neutron <imprec@gmail.com>
*
* @internal
*/
class WindowsPipes extends AbstractPipes
{
/** @var array */
private $files = array();
/** @var array */
private $fileHandles = array();
/** @var array */
private $readBytes = array(
Process::STDOUT => 0,
Process::STDERR => 0,
);
/** @var bool */
private $haveReadSupport;
public function __construct($input, $haveReadSupport)
{
$this->haveReadSupport = (bool) $haveReadSupport;
if ($this->haveReadSupport) {
// Fix for PHP bug #51800: reading from STDOUT pipe hangs forever on Windows if the output is too big.
// Workaround for this problem is to use temporary files instead of pipes on Windows platform.
//
// @see https://bugs.php.net/bug.php?id=51800
$pipes = array(
Process::STDOUT => Process::OUT,
Process::STDERR => Process::ERR,
);
$tmpCheck = false;
$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 (file_exists($file) && !unlink($file)) {
continue 2;
}
$h = fopen($file, 'xb');
if (!$h) {
$error = $lastError;
if ($tmpCheck || $tmpCheck = unlink(tempnam(false, 'sf_check_'))) {
continue;
}
restore_error_handler();
throw new RuntimeException(sprintf('A temporary file could not be opened to write the process output: %s', $error));
}
if (!$h || !$this->fileHandles[$pipe] = fopen($file, 'rb')) {
continue 2;
}
if (isset($this->files[$pipe])) {
unlink($this->files[$pipe]);
}
$this->files[$pipe] = $file;
}
break;
}
restore_error_handler();
}
parent::__construct($input);
}
public function __destruct()
{
$this->close();
$this->removeFiles();
}
/**
* {@inheritdoc}
*/
public function getDescriptors()
{
if (!$this->haveReadSupport) {
$nullstream = fopen('NUL', 'c');
return array(
array('pipe', 'r'),
$nullstream,
$nullstream,
);
}
// We're not using pipe on Windows platform as it hangs (https://bugs.php.net/bug.php?id=51800)
// We're not using file handles as it can produce corrupted output https://bugs.php.net/bug.php?id=65650
// So we redirect output within the commandline and pass the nul device to the process
return array(
array('pipe', 'r'),
array('file', 'NUL', 'w'),
array('file', 'NUL', 'w'),
);
}
/**
* {@inheritdoc}
*/
public function getFiles()
{
return $this->files;
}
/**
* {@inheritdoc}
*/
public function readAndWrite($blocking, $close = false)
{
$this->unblock();
$w = $this->write();
$read = $r = $e = array();
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) {
fclose($fileHandle);
unset($this->fileHandles[$type]);
}
}
return $read;
}
/**
* {@inheritdoc}
*/
public function haveReadSupport()
{
return $this->haveReadSupport;
}
/**
* {@inheritdoc}
*/
public function areOpen()
{
return $this->pipes && $this->fileHandles;
}
/**
* {@inheritdoc}
*/
public function close()
{
parent::close();
foreach ($this->fileHandles as $handle) {
fclose($handle);
}
$this->fileHandles = array();
}
/**
* Removes temporary files.
*/
private function removeFiles()
{
foreach ($this->files as $filename) {
if (file_exists($filename)) {
@unlink($filename);
}
}
$this->files = array();
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Process;
use Symfony\Component\Process\Exception\InvalidArgumentException;
use Symfony\Component\Process\Exception\LogicException;
use Symfony\Component\Process\Exception\ProcessFailedException;
use Symfony\Component\Process\Exception\ProcessTimedOutException;
use Symfony\Component\Process\Exception\RuntimeException;
use Symfony\Component\Process\Pipes\PipesInterface;
use Symfony\Component\Process\Pipes\UnixPipes;
use Symfony\Component\Process\Pipes\WindowsPipes;
/**
* Process is a thin wrapper around proc_* functions to easily
* start independent PHP processes.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Romain Neutron <imprec@gmail.com>
*/
class Process implements \IteratorAggregate
{
const ERR = 'err';
const OUT = 'out';
const STATUS_READY = 'ready';
const STATUS_STARTED = 'started';
const STATUS_TERMINATED = 'terminated';
const STDIN = 0;
const STDOUT = 1;
const STDERR = 2;
// Timeout Precision in seconds.
const TIMEOUT_PRECISION = 0.2;
const ITER_NON_BLOCKING = 1; // By default, iterating over outputs is a blocking call, use this flag to make it non-blocking
const ITER_KEEP_OUTPUT = 2; // By default, outputs are cleared while iterating, use this flag to keep them in memory
const ITER_SKIP_OUT = 4; // Use this flag to skip STDOUT while iterating
const ITER_SKIP_ERR = 8; // Use this flag to skip STDERR while iterating
private $callback;
private $hasCallback = false;
private $commandline;
private $cwd;
private $env;
private $input;
private $starttime;
private $lastOutputTime;
private $timeout;
private $idleTimeout;
private $options;
private $exitcode;
private $fallbackStatus = array();
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;
private $pty;
private $useFileHandles = false;
/** @var PipesInterface */
private $processPipes;
private $latestSignal;
private static $sigchild;
/**
* Exit codes translation table.
*
* User-defined errors must use exit codes in the 64-113 range.
*
* @var array
*/
public static $exitCodes = array(
0 => 'OK',
1 => 'General error',
2 => 'Misuse of shell builtins',
126 => 'Invoked command cannot execute',
127 => 'Command not found',
128 => 'Invalid exit argument',
// signals
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)',
// 144 - not defined
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',
// 156 - not defined
157 => 'Pollable event',
// 158 - not defined
159 => 'Bad syscall',
);
/**
* Constructor.
*
* @param string $commandline The command line to run
* @param string|null $cwd The working directory or null to use the working dir of the current PHP process
* @param array|null $env The environment variables or null to use the same environment as the current PHP process
* @param mixed|null $input The input as stream resource, scalar or \Traversable, or null for no input
* @param int|float|null $timeout The timeout in seconds or null to disable
* @param array $options An array of options for proc_open
*
* @throws RuntimeException When proc_open is not installed
*/
public function __construct($commandline, $cwd = null, array $env = null, $input = null, $timeout = 60, array $options = array())
{
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;
// on Windows, if the cwd changed via chdir(), proc_open defaults to the dir where PHP was started
// on Gnu/Linux, PHP builds with --enable-maintainer-zts are also affected
// @see : https://bugs.php.net/bug.php?id=51800
// @see : https://bugs.php.net/bug.php?id=50524
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->enhanceWindowsCompatibility = true;
$this->enhanceSigchildCompatibility = '\\' !== DIRECTORY_SEPARATOR && $this->isSigchildEnabled();
$this->options = array_replace(array('suppress_errors' => true, 'binary_pipes' => true), $options);
}
public function __destruct()
{
$this->stop(0);
}
public function __clone()
{
$this->resetProcessData();
}
/**
* Runs the process.
*
* The callback receives the type of output (out or err) and
* some bytes from the output in real-time. It allows to have feedback
* from the independent process during execution.
*
* The STDOUT and STDERR are also available after the process is finished
* via the getOutput() and getErrorOutput() methods.
*
* @param callable|null $callback A PHP callback to run whenever there is some
* output available on STDOUT or STDERR
*
* @return int The exit status code
*
* @throws RuntimeException When process can't be launched
* @throws RuntimeException When process stopped after receiving signal
* @throws LogicException In case a callback is provided and output has been disabled
*/
public function run($callback = null)
{
$this->start($callback);
return $this->wait();
}
/**
* Runs the process.
*
* This is identical to run() except that an exception is thrown if the process
* exits with a non-zero exit code.
*
* @param callable|null $callback
*
* @return self
*
* @throws RuntimeException if PHP was compiled with --enable-sigchild and the enhanced sigchild compatibility mode is not enabled
* @throws ProcessFailedException if the process didn't terminate successfully
*/
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.');
}
if (0 !== $this->run($callback)) {
throw new ProcessFailedException($this);
}
return $this;
}
/**
* Starts the process and returns after writing the input to STDIN.
*
* This method blocks until all STDIN data is sent to the process then it
* returns while the process runs in the background.
*
* The termination of the process can be awaited with wait().
*
* The callback receives the type of output (out or err) and some bytes from
* the output in real-time while writing the standard input to the process.
* It allows to have feedback from the independent process during execution.
*
* @param callable|null $callback A PHP callback to run whenever there is some
* output available on STDOUT or STDERR
*
* @throws RuntimeException When process can't be launched
* @throws RuntimeException When process is already running
* @throws LogicException In case a callback is provided and output has been disabled
*/
public function start(callable $callback = null)
{
if ($this->isRunning()) {
throw new RuntimeException('Process is already running');
}
$this->resetProcessData();
$this->starttime = $this->lastOutputTime = microtime(true);
$this->callback = $this->buildCallback($callback);
$this->hasCallback = null !== $callback;
$descriptors = $this->getDescriptors();
$commandline = $this->commandline;
if ('\\' === DIRECTORY_SEPARATOR && $this->enhanceWindowsCompatibility) {
$commandline = 'cmd /V:ON /E:ON /D /C "('.$commandline.')';
foreach ($this->processPipes->getFiles() as $offset => $filename) {
$commandline .= ' '.$offset.'>'.ProcessUtils::escapeArgument($filename);
}
$commandline .= '"';
if (!isset($this->options['bypass_shell'])) {
$this->options['bypass_shell'] = true;
}
} elseif (!$this->useFileHandles && $this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) {
// last exit code is output on the fourth pipe and caught to work around --enable-sigchild
$descriptors[3] = array('pipe', 'w');
// See https://unix.stackexchange.com/questions/71205/background-process-pipe-input
$commandline = '{ ('.$this->commandline.') <&3 3<&- 3>/dev/null & } 3<&0;';
$commandline .= 'pid=$!; echo $pid >&3; wait $pid; code=$?; echo $code >&3; exit $code';
// Workaround for the bug, when PTS functionality is enabled.
// @see : https://bugs.php.net/69442
$ptsWorkaround = fopen(__FILE__, 'r');
}
$this->process = proc_open($commandline, $descriptors, $this->processPipes->pipes, $this->cwd, $this->env, $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();
}
/**
* Restarts the process.
*
* Be warned that the process is cloned before being started.
*
* @param callable|null $callback A PHP callback to run whenever there is some
* output available on STDOUT or STDERR
*
* @return Process The new process
*
* @throws RuntimeException When process can't be launched
* @throws RuntimeException When process is already running
*
* @see start()
*/
public function restart(callable $callback = null)
{
if ($this->isRunning()) {
throw new RuntimeException('Process is already running');
}
$process = clone $this;
$process->start($callback);
return $process;
}
/**
* Waits for the process to terminate.
*
* The callback receives the type of output (out or err) and some bytes
* from the output in real-time while writing the standard input to the process.
* It allows to have feedback from the independent process during execution.
*
* @param callable|null $callback A valid PHP callback
*
* @return int The exitcode of the process
*
* @throws RuntimeException When process timed out
* @throws RuntimeException When process stopped after receiving signal
* @throws LogicException When process is not yet started
*/
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()) {
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;
}
/**
* Returns the Pid (process identifier), if applicable.
*
* @return int|null The process id if running, null otherwise
*/
public function getPid()
{
return $this->isRunning() ? $this->processInformation['pid'] : null;
}
/**
* Sends a POSIX signal to the process.
*
* @param int $signal A valid POSIX signal (see http://www.php.net/manual/en/pcntl.constants.php)
*
* @return Process
*
* @throws LogicException In case the process is not running
* @throws RuntimeException In case --enable-sigchild is activated and the process can't be killed
* @throws RuntimeException In case of failure
*/
public function signal($signal)
{
$this->doSignal($signal, true);
return $this;
}
/**
* Disables fetching output and error output from the underlying process.
*
* @return Process
*
* @throws RuntimeException In case the process is already running
* @throws LogicException if an idle timeout is set
*/
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;
}
/**
* Enables fetching output and error output from the underlying process.
*
* @return Process
*
* @throws RuntimeException In case the process is already running
*/
public function enableOutput()
{
if ($this->isRunning()) {
throw new RuntimeException('Enabling output while the process is running is not possible.');
}
$this->outputDisabled = false;
return $this;
}
/**
* Returns true in case the output is disabled, false otherwise.
*
* @return bool
*/
public function isOutputDisabled()
{
return $this->outputDisabled;
}
/**
* Returns the current output of the process (STDOUT).
*
* @return string The process output
*
* @throws LogicException in case the output has been disabled
* @throws LogicException In case the process is not started
*/
public function getOutput()
{
$this->readPipesForOutput(__FUNCTION__);
if (false === $ret = stream_get_contents($this->stdout, -1, 0)) {
return '';
}
return $ret;
}
/**
* Returns the output incrementally.
*
* In comparison with the getOutput method which always return the whole
* output, this one returns the new output since the last call.
*
* @return string The process output since the last call
*
* @throws LogicException in case the output has been disabled
* @throws LogicException In case the process is not started
*/
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;
}
/**
* Returns an iterator to the output of the process, with the output type as keys (Process::OUT/ERR).
*
* @param int $flags A bit field of Process::ITER_* flags
*
* @throws LogicException in case the output has been disabled
* @throws LogicException In case the process is not started
*
* @return \Generator
*/
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->readPipesForOutput(__FUNCTION__, $blocking);
}
}
/**
* Clears the process output.
*
* @return Process
*/
public function clearOutput()
{
ftruncate($this->stdout, 0);
fseek($this->stdout, 0);
$this->incrementalOutputOffset = 0;
return $this;
}
/**
* Returns the current error output of the process (STDERR).
*
* @return string The process error output
*
* @throws LogicException in case the output has been disabled
* @throws LogicException In case the process is not started
*/
public function getErrorOutput()
{
$this->readPipesForOutput(__FUNCTION__);
if (false === $ret = stream_get_contents($this->stderr, -1, 0)) {
return '';
}
return $ret;
}
/**
* Returns the errorOutput incrementally.
*
* In comparison with the getErrorOutput method which always return the
* whole error output, this one returns the new error output since the last
* call.
*
* @return string The process error output since the last call
*
* @throws LogicException in case the output has been disabled
* @throws LogicException In case the process is not started
*/
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;
}
/**
* Clears the process output.
*
* @return Process
*/
public function clearErrorOutput()
{
ftruncate($this->stderr, 0);
fseek($this->stderr, 0);
$this->incrementalErrorOutputOffset = 0;
return $this;
}
/**
* Returns the exit code returned by the process.
*
* @return null|int The exit status code, null if the Process is not terminated
*
* @throws RuntimeException In case --enable-sigchild is activated and the sigchild compatibility mode is disabled
*/
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;
}
/**
* Returns a string representation for the exit code returned by the process.
*
* This method relies on the Unix exit code status standardization
* and might not be relevant for other operating systems.
*
* @return null|string A string representation for the exit status code, null if the Process is not terminated
*
* @see http://tldp.org/LDP/abs/html/exitcodes.html
* @see http://en.wikipedia.org/wiki/Unix_signal
*/
public function getExitCodeText()
{
if (null === $exitcode = $this->getExitCode()) {
return;
}
return isset(self::$exitCodes[$exitcode]) ? self::$exitCodes[$exitcode] : 'Unknown error';
}
/**
* Checks if the process ended successfully.
*
* @return bool true if the process ended successfully, false otherwise
*/
public function isSuccessful()
{
return 0 === $this->getExitCode();
}
/**
* Returns true if the child process has been terminated by an uncaught signal.
*
* It always returns false on Windows.
*
* @return bool
*
* @throws RuntimeException In case --enable-sigchild is activated
* @throws LogicException In case the process is not terminated
*/
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'];
}
/**
* Returns the number of the signal that caused the child process to terminate its execution.
*
* It is only meaningful if hasBeenSignaled() returns true.
*
* @return int
*
* @throws RuntimeException In case --enable-sigchild is activated
* @throws LogicException In case the process is not terminated
*/
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'];
}
/**
* Returns true if the child process has been stopped by a signal.
*
* It always returns false on Windows.
*
* @return bool
*
* @throws LogicException In case the process is not terminated
*/
public function hasBeenStopped()
{
$this->requireProcessIsTerminated(__FUNCTION__);
return $this->processInformation['stopped'];
}
/**
* Returns the number of the signal that caused the child process to stop its execution.
*
* It is only meaningful if hasBeenStopped() returns true.
*
* @return int
*
* @throws LogicException In case the process is not terminated
*/
public function getStopSignal()
{
$this->requireProcessIsTerminated(__FUNCTION__);
return $this->processInformation['stopsig'];
}
/**
* Checks if the process is currently running.
*
* @return bool true if the process is currently running, false otherwise
*/
public function isRunning()
{
if (self::STATUS_STARTED !== $this->status) {
return false;
}
$this->updateStatus(false);
return $this->processInformation['running'];
}
/**
* Checks if the process has been started with no regard to the current state.
*
* @return bool true if status is ready, false otherwise
*/
public function isStarted()
{
return $this->status != self::STATUS_READY;
}
/**
* Checks if the process is terminated.
*
* @return bool true if process is terminated, false otherwise
*/
public function isTerminated()
{
$this->updateStatus(false);
return $this->status == self::STATUS_TERMINATED;
}
/**
* Gets the process status.
*
* The status is one of: ready, started, terminated.
*
* @return string The current process status
*/
public function getStatus()
{
$this->updateStatus(false);
return $this->status;
}
/**
* Stops the process.
*
* @param int|float $timeout The timeout in seconds
* @param int $signal A POSIX signal to send in case the process has not stop at timeout, default is SIGKILL (9)
*
* @return int The exit-code of the process
*/
public function stop($timeout = 10, $signal = null)
{
$timeoutMicro = microtime(true) + $timeout;
if ($this->isRunning()) {
// given `SIGTERM` may not be defined and that `proc_terminate` uses the constant value and not the constant itself, we use the same here
$this->doSignal(15, false);
do {
usleep(1000);
} while ($this->isRunning() && microtime(true) < $timeoutMicro);
if ($this->isRunning()) {
// Avoid exception here: process is supposed to be running, but it might have stopped just
// after this line. In any case, let's silently discard the error, we cannot do anything.
$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;
}
/**
* Adds a line to the STDOUT stream.
*
* @internal
*
* @param string $line The line to append
*/
public function addOutput($line)
{
$this->lastOutputTime = microtime(true);
fseek($this->stdout, 0, SEEK_END);
fwrite($this->stdout, $line);
fseek($this->stdout, $this->incrementalOutputOffset);
}
/**
* Adds a line to the STDERR stream.
*
* @internal
*
* @param string $line The line to append
*/
public function addErrorOutput($line)
{
$this->lastOutputTime = microtime(true);
fseek($this->stderr, 0, SEEK_END);
fwrite($this->stderr, $line);
fseek($this->stderr, $this->incrementalErrorOutputOffset);
}
/**
* Gets the command line to be executed.
*
* @return string The command to execute
*/
public function getCommandLine()
{
return $this->commandline;
}
/**
* Sets the command line to be executed.
*
* @param string $commandline The command to execute
*
* @return self The current Process instance
*/
public function setCommandLine($commandline)
{
$this->commandline = $commandline;
return $this;
}
/**
* Gets the process timeout (max. runtime).
*
* @return float|null The timeout in seconds or null if it's disabled
*/
public function getTimeout()
{
return $this->timeout;
}
/**
* Gets the process idle timeout (max. time since last output).
*
* @return float|null The timeout in seconds or null if it's disabled
*/
public function getIdleTimeout()
{
return $this->idleTimeout;
}
/**
* Sets the process timeout (max. runtime).
*
* To disable the timeout, set this value to null.
*
* @param int|float|null $timeout The timeout in seconds
*
* @return self The current Process instance
*
* @throws InvalidArgumentException if the timeout is negative
*/
public function setTimeout($timeout)
{
$this->timeout = $this->validateTimeout($timeout);
return $this;
}
/**
* Sets the process idle timeout (max. time since last output).
*
* To disable the timeout, set this value to null.
*
* @param int|float|null $timeout The timeout in seconds
*
* @return self The current Process instance
*
* @throws LogicException if the output is disabled
* @throws InvalidArgumentException if the timeout is negative
*/
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;
}
/**
* Enables or disables the TTY mode.
*
* @param bool $tty True to enabled and false to disable
*
* @return self The current Process instance
*
* @throws RuntimeException In case the TTY mode is not supported
*/
public function setTty($tty)
{
if ('\\' === DIRECTORY_SEPARATOR && $tty) {
throw new RuntimeException('TTY mode is not supported on Windows platform.');
}
if ($tty && (!file_exists('/dev/tty') || !is_readable('/dev/tty'))) {
throw new RuntimeException('TTY mode requires /dev/tty to be readable.');
}
$this->tty = (bool) $tty;
return $this;
}
/**
* Checks if the TTY mode is enabled.
*
* @return bool true if the TTY mode is enabled, false otherwise
*/
public function isTty()
{
return $this->tty;
}
/**
* Sets PTY mode.
*
* @param bool $bool
*
* @return self
*/
public function setPty($bool)
{
$this->pty = (bool) $bool;
return $this;
}
/**
* Returns PTY state.
*
* @return bool
*/
public function isPty()
{
return $this->pty;
}
/**
* Gets the working directory.
*
* @return string|null The current working directory or null on failure
*/
public function getWorkingDirectory()
{
if (null === $this->cwd) {
// getcwd() will return false if any one of the parent directories does not have
// the readable or search mode set, even if the current directory does
return getcwd() ?: null;
}
return $this->cwd;
}
/**
* Sets the current working directory.
*
* @param string $cwd The new working directory
*
* @return self The current Process instance
*/
public function setWorkingDirectory($cwd)
{
$this->cwd = $cwd;
return $this;
}
/**
* Gets the environment variables.
*
* @return array The current environment variables
*/
public function getEnv()
{
return $this->env;
}
/**
* Sets the environment variables.
*
* An environment variable value should be a string.
* If it is an array, the variable is ignored.
*
* That happens in PHP when 'argv' is registered into
* the $_ENV array for instance.
*
* @param array $env The new environment variables
*
* @return self The current Process instance
*/
public function setEnv(array $env)
{
// Process can not handle env values that are arrays
$env = array_filter($env, function ($value) {
return !is_array($value);
});
$this->env = array();
foreach ($env as $key => $value) {
$this->env[$key] = (string) $value;
}
return $this;
}
/**
* Gets the Process input.
*
* @return resource|string|\Iterator|null The Process input
*/
public function getInput()
{
return $this->input;
}
/**
* Sets the input.
*
* This content will be passed to the underlying process standard input.
*
* @param resource|scalar|\Traversable|null $input The content
*
* @return self The current Process instance
*
* @throws LogicException In case the process is running
*/
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;
}
/**
* Gets the options for proc_open.
*
* @return array The current options
*/
public function getOptions()
{
return $this->options;
}
/**
* Sets the options for proc_open.
*
* @param array $options The new options
*
* @return self The current Process instance
*/
public function setOptions(array $options)
{
$this->options = $options;
return $this;
}
/**
* Gets whether or not Windows compatibility is enabled.
*
* This is true by default.
*
* @return bool
*/
public function getEnhanceWindowsCompatibility()
{
return $this->enhanceWindowsCompatibility;
}
/**
* Sets whether or not Windows compatibility is enabled.
*
* @param bool $enhance
*
* @return self The current Process instance
*/
public function setEnhanceWindowsCompatibility($enhance)
{
$this->enhanceWindowsCompatibility = (bool) $enhance;
return $this;
}
/**
* Returns whether sigchild compatibility mode is activated or not.
*
* @return bool
*/
public function getEnhanceSigchildCompatibility()
{
return $this->enhanceSigchildCompatibility;
}
/**
* Activates sigchild compatibility mode.
*
* Sigchild compatibility mode is required to get the exit code and
* determine the success of a process when PHP has been compiled with
* the --enable-sigchild option
*
* @param bool $enhance
*
* @return self The current Process instance
*/
public function setEnhanceSigchildCompatibility($enhance)
{
$this->enhanceSigchildCompatibility = (bool) $enhance;
return $this;
}
/**
* Performs a check between the timeout definition and the time the process started.
*
* In case you run a background process (with the start method), you should
* trigger this method regularly to ensure the process timeout
*
* @throws ProcessTimedOutException In case the timeout was reached
*/
public function checkTimeout()
{
if ($this->status !== self::STATUS_STARTED) {
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);
}
}
/**
* Returns whether PTY is supported on the current operating system.
*
* @return bool
*/
public static function isPtySupported()
{
static $result;
if (null !== $result) {
return $result;
}
if ('\\' === DIRECTORY_SEPARATOR) {
return $result = false;
}
return $result = (bool) @proc_open('echo 1', array(array('pty'), array('pty'), array('pty')), $pipes);
}
/**
* Creates the descriptors needed by the proc_open.
*
* @return array
*/
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();
}
/**
* Builds up the callback used by wait().
*
* The callbacks adds all occurred output to the specific buffer and calls
* the user callback (if present) with the received output.
*
* @param callable|null $callback The user defined PHP callback
*
* @return \Closure A PHP closure
*/
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);
}
};
}
/**
* Updates the status of the process, reads pipes.
*
* @param bool $blocking Whether to use a blocking read call
*/
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();
}
}
/**
* Returns whether PHP has been compiled with the '--enable-sigchild' option or not.
*
* @return bool
*/
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');
}
/**
* Reads pipes for the freshest output.
*
* @param string $caller The name of the method that needs fresh outputs
* @param bool $blocking Whether to use blocking calls or not
*
* @throws LogicException in case output has been disabled or process is not started
*/
private function readPipesForOutput($caller, $blocking = false)
{
if ($this->outputDisabled) {
throw new LogicException('Output has been disabled.');
}
$this->requireProcessIsStarted($caller);
$this->updateStatus($blocking);
}
/**
* Validates and returns the filtered timeout.
*
* @param int|float|null $timeout
*
* @return float|null
*
* @throws InvalidArgumentException if the given timeout is a negative number
*/
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;
}
/**
* Reads pipes, executes callback.
*
* @param bool $blocking Whether to use blocking calls or not
* @param bool $close Whether to close file handles or not
*/
private function readPipes($blocking, $close)
{
$result = $this->processPipes->readAndWrite($blocking, $close);
$callback = $this->callback;
foreach ($result as $type => $data) {
if (3 !== $type) {
$callback($type === self::STDOUT ? self::OUT : self::ERR, $data);
} elseif (!isset($this->fallbackStatus['signaled'])) {
$this->fallbackStatus['exitcode'] = (int) $data;
}
}
}
/**
* Closes process resource, closes file handles, sets the exitcode.
*
* @return int The exitcode
*/
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']) {
// if process has been signaled, no exitcode but a valid termsig, apply Unix convention
$this->exitcode = 128 + $this->processInformation['termsig'];
} elseif ($this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) {
$this->processInformation['signaled'] = true;
$this->processInformation['termsig'] = -1;
}
}
// Free memory from self-reference callback created by buildCallback
// Doing so in other contexts like __destruct or by garbage collector is ineffective
// Now pipes are closed, so the callback is no longer necessary
$this->callback = null;
return $this->exitcode;
}
/**
* Resets data related to the latest run of the process.
*/
private function resetProcessData()
{
$this->starttime = null;
$this->callback = null;
$this->exitcode = null;
$this->fallbackStatus = array();
$this->processInformation = null;
$this->stdout = fopen('php://temp/maxmemory:'.(1024 * 1024), 'wb+');
$this->stderr = fopen('php://temp/maxmemory:'.(1024 * 1024), 'wb+');
$this->process = null;
$this->latestSignal = null;
$this->status = self::STATUS_READY;
$this->incrementalOutputOffset = 0;
$this->incrementalErrorOutputOffset = 0;
}
/**
* Sends a POSIX signal to the process.
*
* @param int $signal A valid POSIX signal (see http://www.php.net/manual/en/pcntl.constants.php)
* @param bool $throwException Whether to throw exception in case signal failed
*
* @return bool True if the signal was sent successfully, false otherwise
*
* @throws LogicException In case the process is not running
* @throws RuntimeException In case --enable-sigchild is activated and the process can't be killed
* @throws RuntimeException In case of failure
*/
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), array(2 => array('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;
}
/**
* Ensures the process is running or terminated, throws a LogicException if the process has a not started.
*
* @param string $functionName The function name that was called
*
* @throws LogicException If the process has not run.
*/
private function requireProcessIsStarted($functionName)
{
if (!$this->isStarted()) {
throw new LogicException(sprintf('Process must be started before calling %s.', $functionName));
}
}
/**
* Ensures the process is terminated, throws a LogicException if the process has a status different than `terminated`.
*
* @param string $functionName The function name that was called
*
* @throws LogicException If the process is not yet terminated.
*/
private function requireProcessIsTerminated($functionName)
{
if (!$this->isTerminated()) {
throw new LogicException(sprintf('Process must be terminated before calling %s.', $functionName));
}
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Process;
use Symfony\Component\Process\Exception\InvalidArgumentException;
use Symfony\Component\Process\Exception\LogicException;
/**
* Process builder.
*
* @author Kris Wallsmith <kris@symfony.com>
*/
class ProcessBuilder
{
private $arguments;
private $cwd;
private $env = array();
private $input;
private $timeout = 60;
private $options = array();
private $inheritEnv = true;
private $prefix = array();
private $outputDisabled = false;
/**
* Constructor.
*
* @param string[] $arguments An array of arguments
*/
public function __construct(array $arguments = array())
{
$this->arguments = $arguments;
}
/**
* Creates a process builder instance.
*
* @param string[] $arguments An array of arguments
*
* @return ProcessBuilder
*/
public static function create(array $arguments = array())
{
return new static($arguments);
}
/**
* Adds an unescaped argument to the command string.
*
* @param string $argument A command argument
*
* @return ProcessBuilder
*/
public function add($argument)
{
$this->arguments[] = $argument;
return $this;
}
/**
* Adds a prefix to the command string.
*
* The prefix is preserved when resetting arguments.
*
* @param string|array $prefix A command prefix or an array of command prefixes
*
* @return ProcessBuilder
*/
public function setPrefix($prefix)
{
$this->prefix = is_array($prefix) ? $prefix : array($prefix);
return $this;
}
/**
* Sets the arguments of the process.
*
* Arguments must not be escaped.
* Previous arguments are removed.
*
* @param string[] $arguments
*
* @return ProcessBuilder
*/
public function setArguments(array $arguments)
{
$this->arguments = $arguments;
return $this;
}
/**
* Sets the working directory.
*
* @param null|string $cwd The working directory
*
* @return ProcessBuilder
*/
public function setWorkingDirectory($cwd)
{
$this->cwd = $cwd;
return $this;
}
/**
* Sets whether environment variables will be inherited or not.
*
* @param bool $inheritEnv
*
* @return ProcessBuilder
*/
public function inheritEnvironmentVariables($inheritEnv = true)
{
$this->inheritEnv = $inheritEnv;
return $this;
}
/**
* Sets an environment variable.
*
* Setting a variable overrides its previous value. Use `null` to unset a
* defined environment variable.
*
* @param string $name The variable name
* @param null|string $value The variable value
*
* @return ProcessBuilder
*/
public function setEnv($name, $value)
{
$this->env[$name] = $value;
return $this;
}
/**
* Adds a set of environment variables.
*
* Already existing environment variables with the same name will be
* overridden by the new values passed to this method. Pass `null` to unset
* a variable.
*
* @param array $variables The variables
*
* @return ProcessBuilder
*/
public function addEnvironmentVariables(array $variables)
{
$this->env = array_replace($this->env, $variables);
return $this;
}
/**
* Sets the input of the process.
*
* @param resource|scalar|\Traversable|null $input The input content
*
* @return ProcessBuilder
*
* @throws InvalidArgumentException In case the argument is invalid
*/
public function setInput($input)
{
$this->input = ProcessUtils::validateInput(__METHOD__, $input);
return $this;
}
/**
* Sets the process timeout.
*
* To disable the timeout, set this value to null.
*
* @param float|null $timeout
*
* @return ProcessBuilder
*
* @throws InvalidArgumentException
*/
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;
}
/**
* Adds a proc_open option.
*
* @param string $name The option name
* @param string $value The option value
*
* @return ProcessBuilder
*/
public function setOption($name, $value)
{
$this->options[$name] = $value;
return $this;
}
/**
* Disables fetching output and error output from the underlying process.
*
* @return ProcessBuilder
*/
public function disableOutput()
{
$this->outputDisabled = true;
return $this;
}
/**
* Enables fetching output and error output from the underlying process.
*
* @return ProcessBuilder
*/
public function enableOutput()
{
$this->outputDisabled = false;
return $this;
}
/**
* Creates a Process instance and returns it.
*
* @return Process
*
* @throws LogicException In case no arguments have been provided
*/
public function getProcess()
{
if (0 === count($this->prefix) && 0 === count($this->arguments)) {
throw new LogicException('You must add() command arguments before calling getProcess().');
}
$options = $this->options;
$arguments = array_merge($this->prefix, $this->arguments);
$script = implode(' ', array_map(array(__NAMESPACE__.'\\ProcessUtils', 'escapeArgument'), $arguments));
if ($this->inheritEnv) {
$env = array_replace($_ENV, $_SERVER, $this->env);
} else {
$env = $this->env;
}
$process = new Process($script, $this->cwd, $env, $this->input, $this->timeout, $options);
if ($this->outputDisabled) {
$process->disableOutput();
}
return $process;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Process;
use Symfony\Component\Process\Exception\InvalidArgumentException;
/**
* ProcessUtils is a bunch of utility methods.
*
* This class contains static methods only and is not meant to be instantiated.
*
* @author Martin Hasoň <martin.hason@gmail.com>
*/
class ProcessUtils
{
/**
* This class should not be instantiated.
*/
private function __construct()
{
}
/**
* Escapes a string to be used as a shell argument.
*
* @param string $argument The argument that will be escaped
*
* @return string The escaped argument
*/
public static function escapeArgument($argument)
{
//Fix for PHP bug #43784 escapeshellarg removes % from given string
//Fix for PHP bug #49446 escapeshellarg doesn't work on Windows
//@see https://bugs.php.net/bug.php?id=43784
//@see https://bugs.php.net/bug.php?id=49446
if ('\\' === DIRECTORY_SEPARATOR) {
if ('' === $argument) {
return escapeshellarg($argument);
}
$escapedArgument = '';
$quote = false;
foreach (preg_split('/(")/', $argument, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE) as $part) {
if ('"' === $part) {
$escapedArgument .= '\\"';
} elseif (self::isSurroundedBy($part, '%')) {
// Avoid environment variable expansion
$escapedArgument .= '^%"'.substr($part, 1, -1).'"^%';
} else {
// escape trailing backslash
if ('\\' === substr($part, -1)) {
$part .= '\\';
}
$quote = true;
$escapedArgument .= $part;
}
}
if ($quote) {
$escapedArgument = '"'.$escapedArgument.'"';
}
return $escapedArgument;
}
return escapeshellarg($argument);
}
/**
* Validates and normalizes a Process input.
*
* @param string $caller The name of method call that validates the input
* @param mixed $input The input to validate
*
* @return mixed The validated input
*
* @throws InvalidArgumentException In case the input is not valid
*/
public static function validateInput($caller, $input)
{
if (null !== $input) {
if (is_resource($input)) {
return $input;
}
if (is_string($input)) {
return $input;
}
if (is_scalar($input)) {
return (string) $input;
}
if ($input instanceof Process) {
return $input->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];
}
}
<?php
/*
* This file is part of the webmozart/assert package.
*
* (c) Bernhard Schussek <bschussek@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Webmozart\Assert;
use BadMethodCallException;
use InvalidArgumentException;
use Traversable;
/**
* Efficient assertions to validate the input/output of your methods.
*
* @method static void nullOrString($value, $message = '')
* @method static void nullOrStringNotEmpty($value, $message = '')
* @method static void nullOrInteger($value, $message = '')
* @method static void nullOrIntegerish($value, $message = '')
* @method static void nullOrFloat($value, $message = '')
* @method static void nullOrNumeric($value, $message = '')
* @method static void nullOrBoolean($value, $message = '')
* @method static void nullOrScalar($value, $message = '')
* @method static void nullOrObject($value, $message = '')
* @method static void nullOrResource($value, $type = null, $message = '')
* @method static void nullOrIsCallable($value, $message = '')
* @method static void nullOrIsArray($value, $message = '')
* @method static void nullOrIsTraversable($value, $message = '')
* @method static void nullOrIsInstanceOf($value, $class, $message = '')
* @method static void nullOrNotInstanceOf($value, $class, $message = '')
* @method static void nullOrIsEmpty($value, $message = '')
* @method static void nullOrNotEmpty($value, $message = '')
* @method static void nullOrTrue($value, $message = '')
* @method static void nullOrFalse($value, $message = '')
* @method static void nullOrEq($value, $value2, $message = '')
* @method static void nullOrNotEq($value,$value2, $message = '')
* @method static void nullOrSame($value, $value2, $message = '')
* @method static void nullOrNotSame($value, $value2, $message = '')
* @method static void nullOrGreaterThan($value, $value2, $message = '')
* @method static void nullOrGreaterThanEq($value, $value2, $message = '')
* @method static void nullOrLessThan($value, $value2, $message = '')
* @method static void nullOrLessThanEq($value, $value2, $message = '')
* @method static void nullOrRange($value, $min, $max, $message = '')
* @method static void nullOrOneOf($value, $values, $message = '')
* @method static void nullOrContains($value, $subString, $message = '')
* @method static void nullOrStartsWith($value, $prefix, $message = '')
* @method static void nullOrStartsWithLetter($value, $message = '')
* @method static void nullOrEndsWith($value, $suffix, $message = '')
* @method static void nullOrRegex($value, $pattern, $message = '')
* @method static void nullOrAlpha($value, $message = '')
* @method static void nullOrDigits($value, $message = '')
* @method static void nullOrAlnum($value, $message = '')
* @method static void nullOrLower($value, $message = '')
* @method static void nullOrUpper($value, $message = '')
* @method static void nullOrLength($value, $length, $message = '')
* @method static void nullOrMinLength($value, $min, $message = '')
* @method static void nullOrMaxLength($value, $max, $message = '')
* @method static void nullOrLengthBetween($value, $min, $max, $message = '')
* @method static void nullOrFileExists($value, $message = '')
* @method static void nullOrFile($value, $message = '')
* @method static void nullOrDirectory($value, $message = '')
* @method static void nullOrReadable($value, $message = '')
* @method static void nullOrWritable($value, $message = '')
* @method static void nullOrClassExists($value, $message = '')
* @method static void nullOrSubclassOf($value, $class, $message = '')
* @method static void nullOrImplementsInterface($value, $interface, $message = '')
* @method static void nullOrPropertyExists($value, $property, $message = '')
* @method static void nullOrPropertyNotExists($value, $property, $message = '')
* @method static void nullOrMethodExists($value, $method, $message = '')
* @method static void nullOrMethodNotExists($value, $method, $message = '')
* @method static void nullOrKeyExists($value, $key, $message = '')
* @method static void nullOrKeyNotExists($value, $key, $message = '')
* @method static void nullOrUuid($values, $message = '')
* @method static void allString($values, $message = '')
* @method static void allStringNotEmpty($values, $message = '')
* @method static void allInteger($values, $message = '')
* @method static void allIntegerish($values, $message = '')
* @method static void allFloat($values, $message = '')
* @method static void allNumeric($values, $message = '')
* @method static void allBoolean($values, $message = '')
* @method static void allScalar($values, $message = '')
* @method static void allObject($values, $message = '')
* @method static void allResource($values, $type = null, $message = '')
* @method static void allIsCallable($values, $message = '')
* @method static void allIsArray($values, $message = '')
* @method static void allIsTraversable($values, $message = '')
* @method static void allIsInstanceOf($values, $class, $message = '')
* @method static void allNotInstanceOf($values, $class, $message = '')
* @method static void allNull($values, $message = '')
* @method static void allNotNull($values, $message = '')
* @method static void allIsEmpty($values, $message = '')
* @method static void allNotEmpty($values, $message = '')
* @method static void allTrue($values, $message = '')
* @method static void allFalse($values, $message = '')
* @method static void allEq($values, $value2, $message = '')
* @method static void allNotEq($values,$value2, $message = '')
* @method static void allSame($values, $value2, $message = '')
* @method static void allNotSame($values, $value2, $message = '')
* @method static void allGreaterThan($values, $value2, $message = '')
* @method static void allGreaterThanEq($values, $value2, $message = '')
* @method static void allLessThan($values, $value2, $message = '')
* @method static void allLessThanEq($values, $value2, $message = '')
* @method static void allRange($values, $min, $max, $message = '')
* @method static void allOneOf($values, $values, $message = '')
* @method static void allContains($values, $subString, $message = '')
* @method static void allStartsWith($values, $prefix, $message = '')
* @method static void allStartsWithLetter($values, $message = '')
* @method static void allEndsWith($values, $suffix, $message = '')
* @method static void allRegex($values, $pattern, $message = '')
* @method static void allAlpha($values, $message = '')
* @method static void allDigits($values, $message = '')
* @method static void allAlnum($values, $message = '')
* @method static void allLower($values, $message = '')
* @method static void allUpper($values, $message = '')
* @method static void allLength($values, $length, $message = '')
* @method static void allMinLength($values, $min, $message = '')
* @method static void allMaxLength($values, $max, $message = '')
* @method static void allLengthBetween($values, $min, $max, $message = '')
* @method static void allFileExists($values, $message = '')
* @method static void allFile($values, $message = '')
* @method static void allDirectory($values, $message = '')
* @method static void allReadable($values, $message = '')
* @method static void allWritable($values, $message = '')
* @method static void allClassExists($values, $message = '')
* @method static void allSubclassOf($values, $class, $message = '')
* @method static void allImplementsInterface($values, $interface, $message = '')
* @method static void allPropertyExists($values, $property, $message = '')
* @method static void allPropertyNotExists($values, $property, $message = '')
* @method static void allMethodExists($values, $method, $message = '')
* @method static void allMethodNotExists($values, $method, $message = '')
* @method static void allKeyExists($values, $key, $message = '')
* @method static void allKeyNotExists($values, $key, $message = '')
* @method static void allUuid($values, $message = '')
*
* @since 1.0
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class Assert
{
public static function string($value, $message = '')
{
if (!is_string($value)) {
throw new InvalidArgumentException(sprintf(
$message ?: 'Expected a string. Got: %s',
self::typeToString($value)
));
}
}
public static function stringNotEmpty($value, $message = '')
{
self::string($value, $message);
self::notEmpty($value, $message);
}
public static function integer($value, $message = '')
{
if (!is_int($value)) {
throw new InvalidArgumentException(sprintf(
$message ?: 'Expected an integer. Got: %s',
self::typeToString($value)
));
}
}
public static function integerish($value, $message = '')
{
if (!is_numeric($value) || $value != (int) $value) {
throw new InvalidArgumentException(sprintf(
$message ?: 'Expected an integerish value. Got: %s',
self::typeToString($value)
));
}
}
public static function float($value, $message = '')
{
if (!is_float($value)) {
throw new InvalidArgumentException(sprintf(
$message ?: 'Expected a float. Got: %s',
self::typeToString($value)
));
}
}
public static function numeric($value, $message = '')
{
if (!is_numeric($value)) {
throw new InvalidArgumentException(sprintf(
$message ?: 'Expected a numeric. Got: %s',
self::typeToString($value)
));
}
}
public static function boolean($value, $message = '')
{
if (!is_bool($value)) {
throw new InvalidArgumentException(sprintf(
$message ?: 'Expected a boolean. Got: %s',
self::typeToString($value)
));
}
}
public static function scalar($value, $message = '')
{
if (!is_scalar($value)) {
throw new InvalidArgumentException(sprintf(
$message ?: 'Expected a scalar. Got: %s',
self::typeToString($value)
));
}
}
public static function object($value, $message = '')
{
if (!is_object($value)) {
throw new InvalidArgumentException(sprintf(
$message ?: 'Expected an object. Got: %s',
self::typeToString($value)
));
}
}
public static function resource($value, $type = null, $message = '')
{
if (!is_resource($value)) {
throw new InvalidArgumentException(sprintf(
$message ?: 'Expected a resource. Got: %s',
self::typeToString($value)
));
}
if ($type && $type !== get_resource_type($value)) {
throw new InvalidArgumentException(sprintf(
$message ?: 'Expected a resource of type %2$s. Got: %s',
self::typeToString($value),
$type
));
}
}
public static function isCallable($value, $message = '')
{
if (!is_callable($value)) {
throw new InvalidArgumentException(sprintf(
$message ?: 'Expected a callable. Got: %s',
self::typeToString($value)
));
}
}
public static function isArray($value, $message = '')
{
if (!is_array($value)) {
throw new InvalidArgumentException(sprintf(
$message ?: 'Expected an array. Got: %s',
self::typeToString($value)
));
}
}
public static function isTraversable($value, $message = '')
{
if (!is_array($value) && !($value instanceof Traversable)) {
throw new InvalidArgumentException(sprintf(
$message ?: 'Expected a traversable. Got: %s',
self::typeToString($value)
));
}
}
public static function isInstanceOf($value, $class, $message = '')
{
if (!($value instanceof $class)) {
throw new InvalidArgumentException(sprintf(
$message ?: 'Expected an instance of %2$s. Got: %s',
self::typeToString($value),
$class
));
}
}
public static function notInstanceOf($value, $class, $message = '')
{
if ($value instanceof $class) {
throw new InvalidArgumentException(sprintf(
$message ?: 'Expected an instance other than %2$s. Got: %s',
self::typeToString($value),
$class
));
}
}
public static function isEmpty($value, $message = '')
{
if (!empty($value)) {
throw new InvalidArgumentException(sprintf(
$message ?: 'Expected an empty value. Got: %s',
self::valueToString($value)
));
}
}
public static function notEmpty($value, $message = '')
{
if (empty($value)) {
throw new InvalidArgumentException(sprintf(
$message ?: 'Expected a non-empty value. Got: %s',
self::valueToString($value)
));
}
}
public static function null($value, $message = '')
{
if (null !== $value) {
throw new InvalidArgumentException(sprintf(
$message ?: 'Expected null. Got: %s',
self::valueToString($value)
));
}
}
public static function notNull($value, $message = '')
{
if (null === $value) {
throw new InvalidArgumentException(
$message ?: 'Expected a value other than null.'
);
}
}
public static function true($value, $message = '')
{
if (true !== $value) {
throw new InvalidArgumentException(sprintf(
$message ?: 'Expected a value to be true. Got: %s',
self::valueToString($value)
));
}
}
public static function false($value, $message = '')
{
if (false !== $value) {
throw new InvalidArgumentException(sprintf(
$message ?: 'Expected a value to be false. Got: %s',
self::valueToString($value)
));
}
}
public static function eq($value, $value2, $message = '')
{
if ($value2 != $value) {
throw new InvalidArgumentException(sprintf(
$message ?: 'Expected a value equal to %2$s. Got: %s',
self::valueToString($value),
self::valueToString($value2)
));
}
}
public static function notEq($value, $value2, $message = '')
{
if ($value2 == $value) {
throw new InvalidArgumentException(sprintf(
$message ?: 'Expected a different value than %s.',
self::valueToString($value2)
));
}
}
public static function same($value, $value2, $message = '')
{
if ($value2 !== $value) {
throw new InvalidArgumentException(sprintf(
$message ?: 'Expected a value identical to %2$s. Got: %s',
self::valueToString($value),
self::valueToString($value2)
));
}
}
public static function notSame($value, $value2, $message = '')
{
if ($value2 === $value) {
throw new InvalidArgumentException(sprintf(
$message ?: 'Expected a value not identical to %s.',
self::valueToString($value2)
));
}
}
public static function greaterThan($value, $limit, $message = '')
{
if ($value <= $limit) {
throw new InvalidArgumentException(sprintf(
$message ?: 'Expected a value greater than %2$s. Got: %s',
self::valueToString($value),
self::valueToString($limit)
));
}
}
public static function greaterThanEq($value, $limit, $message = '')
{
if ($value < $limit) {
throw new InvalidArgumentException(sprintf(
$message ?: 'Expected a value greater than or equal to %2$s. Got: %s',
self::valueToString($value),
self::valueToString($limit)
));
}
}
public static function lessThan($value, $limit, $message = '')
{
if ($value >= $limit) {
throw new InvalidArgumentException(sprintf(
$message ?: 'Expected a value less than %2$s. Got: %s',
self::valueToString($value),
self::valueToString($limit)
));
}
}
public static function lessThanEq($value, $limit, $message = '')
{
if ($value > $limit) {
throw new InvalidArgumentException(sprintf(
$message ?: 'Expected a value less than or equal to %2$s. Got: %s',
self::valueToString($value),
self::valueToString($limit)
));
}
}
public static function range($value, $min, $max, $message = '')
{
if ($value < $min || $value > $max) {
throw new InvalidArgumentException(sprintf(
$message ?: 'Expected a value between %2$s and %3$s. Got: %s',
self::valueToString($value),
self::valueToString($min),
self::valueToString($max)
));
}
}
public static function oneOf($value, array $values, $message = '')
{
if (!in_array($value, $values, true)) {
throw new InvalidArgumentException(sprintf(
$message ?: 'Expected one of: %2$s. Got: %s',
self::valueToString($value),
implode(', ', array_map(array(__CLASS__, 'valueToString'), $values))
));
}
}
public static function contains($value, $subString, $message = '')
{
if (false === strpos($value, $subString)) {
throw new InvalidArgumentException(sprintf(
$message ?: 'Expected a value to contain %2$s. Got: %s',
self::valueToString($value),
self::valueToString($subString)
));
}
}
public static function startsWith($value, $prefix, $message = '')
{
if (0 !== strpos($value, $prefix)) {
throw new InvalidArgumentException(sprintf(
$message ?: 'Expected a value to start with %2$s. Got: %s',
self::valueToString($value),
self::valueToString($prefix)
));
}
}
public static function startsWithLetter($value, $message = '')
{
$valid = isset($value[0]);
if ($valid) {
$locale = setlocale(LC_CTYPE, 0);
setlocale(LC_CTYPE, 'C');
$valid = ctype_alpha($value[0]);
setlocale(LC_CTYPE, $locale);
}
if (!$valid) {
throw new InvalidArgumentException(sprintf(
$message ?: 'Expected a value to start with a letter. Got: %s',
self::valueToString($value)
));
}
}
public static function endsWith($value, $suffix, $message = '')
{
if ($suffix !== substr($value, -self::strlen($suffix))) {
throw new InvalidArgumentException(sprintf(
$message ?: 'Expected a value to end with %2$s. Got: %s',
self::valueToString($value),
self::valueToString($suffix)
));
}
}
public static function regex($value, $pattern, $message = '')
{
if (!preg_match($pattern, $value)) {
throw new InvalidArgumentException(sprintf(
$message ?: 'The value %s does not match the expected pattern.',
self::valueToString($value)
));
}
}
public static function alpha($value, $message = '')
{
$locale = setlocale(LC_CTYPE, 0);
setlocale(LC_CTYPE, 'C');
$valid = !ctype_alpha($value);
setlocale(LC_CTYPE, $locale);
if ($valid) {
throw new InvalidArgumentException(sprintf(
$message ?: 'Expected a value to contain only letters. Got: %s',
self::valueToString($value)
));
}
}
public static function digits($value, $message = '')
{
$locale = setlocale(LC_CTYPE, 0);
setlocale(LC_CTYPE, 'C');
$valid = !ctype_digit($value);
setlocale(LC_CTYPE, $locale);
if ($valid) {
throw new InvalidArgumentException(sprintf(
$message ?: 'Expected a value to contain digits only. Got: %s',
self::valueToString($value)
));
}
}
public static function alnum($value, $message = '')
{
$locale = setlocale(LC_CTYPE, 0);
setlocale(LC_CTYPE, 'C');
$valid = !ctype_alnum($value);
setlocale(LC_CTYPE, $locale);
if ($valid) {
throw new InvalidArgumentException(sprintf(
$message ?: 'Expected a value to contain letters and digits only. Got: %s',
self::valueToString($value)
));
}
}
public static function lower($value, $message = '')
{
$locale = setlocale(LC_CTYPE, 0);
setlocale(LC_CTYPE, 'C');
$valid = !ctype_lower($value);
setlocale(LC_CTYPE, $locale);
if ($valid) {
throw new InvalidArgumentException(sprintf(
$message ?: 'Expected a value to contain lowercase characters only. Got: %s',
self::valueToString($value)
));
}
}
public static function upper($value, $message = '')
{
$locale = setlocale(LC_CTYPE, 0);
setlocale(LC_CTYPE, 'C');
$valid = !ctype_upper($value);
setlocale(LC_CTYPE, $locale);
if ($valid) {
throw new InvalidArgumentException(sprintf(
$message ?: 'Expected a value to contain uppercase characters only. Got: %s',
self::valueToString($value)
));
}
}
public static function length($value, $length, $message = '')
{
if ($length !== self::strlen($value)) {
throw new InvalidArgumentException(sprintf(
$message ?: 'Expected a value to contain %2$s characters. Got: %s',
self::valueToString($value),
$length
));
}
}
public static function minLength($value, $min, $message = '')
{
if (self::strlen($value) < $min) {
throw new InvalidArgumentException(sprintf(
$message ?: 'Expected a value to contain at least %2$s characters. Got: %s',
self::valueToString($value),
$min
));
}
}
public static function maxLength($value, $max, $message = '')
{
if (self::strlen($value) > $max) {
throw new InvalidArgumentException(sprintf(
$message ?: 'Expected a value to contain at most %2$s characters. Got: %s',
self::valueToString($value),
$max
));
}
}
public static function lengthBetween($value, $min, $max, $message = '')
{
$length = self::strlen($value);
if ($length < $min || $length > $max) {
throw new InvalidArgumentException(sprintf(
$message ?: 'Expected a value to contain between %2$s and %3$s characters. Got: %s',
self::valueToString($value),
$min,
$max
));
}
}
public static function fileExists($value, $message = '')
{
self::string($value);
if (!file_exists($value)) {
throw new InvalidArgumentException(sprintf(
$message ?: 'The file %s does not exist.',
self::valueToString($value)
));
}
}
public static function file($value, $message = '')
{
self::fileExists($value, $message);
if (!is_file($value)) {
throw new InvalidArgumentException(sprintf(
$message ?: 'The path %s is not a file.',
self::valueToString($value)
));
}
}
public static function directory($value, $message = '')
{
self::fileExists($value, $message);
if (!is_dir($value)) {
throw new InvalidArgumentException(sprintf(
$message ?: 'The path %s is no directory.',
self::valueToString($value)
));
}
}
public static function readable($value, $message = '')
{
if (!is_readable($value)) {
throw new InvalidArgumentException(sprintf(
$message ?: 'The path %s is not readable.',
self::valueToString($value)
));
}
}
public static function writable($value, $message = '')
{
if (!is_writable($value)) {
throw new InvalidArgumentException(sprintf(
$message ?: 'The path %s is not writable.',
self::valueToString($value)
));
}
}
public static function classExists($value, $message = '')
{
if (!class_exists($value)) {
throw new InvalidArgumentException(sprintf(
$message ?: 'Expected an existing class name. Got: %s',
self::valueToString($value)
));
}
}
public static function subclassOf($value, $class, $message = '')
{
if (!is_subclass_of($value, $class)) {
throw new InvalidArgumentException(sprintf(
$message ?: 'Expected a sub-class of %2$s. Got: %s',
self::valueToString($value),
self::valueToString($class)
));
}
}
public static function implementsInterface($value, $interface, $message = '')
{
if (!in_array($interface, class_implements($value))) {
throw new InvalidArgumentException(sprintf(
$message ?: 'Expected an implementation of %2$s. Got: %s',
self::valueToString($value),
self::valueToString($interface)
));
}
}
public static function propertyExists($classOrObject, $property, $message = '')
{
if (!property_exists($classOrObject, $property)) {
throw new InvalidArgumentException(sprintf(
$message ?: 'Expected the property %s to exist.',
self::valueToString($property)
));
}
}
public static function propertyNotExists($classOrObject, $property, $message = '')
{
if (property_exists($classOrObject, $property)) {
throw new InvalidArgumentException(sprintf(
$message ?: 'Expected the property %s to not exist.',
self::valueToString($property)
));
}
}
public static function methodExists($classOrObject, $method, $message = '')
{
if (!method_exists($classOrObject, $method)) {
throw new InvalidArgumentException(sprintf(
$message ?: 'Expected the method %s to exist.',
self::valueToString($method)
));
}
}
public static function methodNotExists($classOrObject, $method, $message = '')
{
if (method_exists($classOrObject, $method)) {
throw new InvalidArgumentException(sprintf(
$message ?: 'Expected the method %s to not exist.',
self::valueToString($method)
));
}
}
public static function keyExists($array, $key, $message = '')
{
if (!array_key_exists($key, $array)) {
throw new InvalidArgumentException(sprintf(
$message ?: 'Expected the key %s to exist.',
self::valueToString($key)
));
}
}
public static function keyNotExists($array, $key, $message = '')
{
if (array_key_exists($key, $array)) {
throw new InvalidArgumentException(sprintf(
$message ?: 'Expected the key %s to not exist.',
self::valueToString($key)
));
}
}
public static function uuid($value, $message = '')
{
$value = str_replace(array('urn:', 'uuid:', '{', '}'), '', $value);
// The nil UUID is special form of UUID that is specified to have all
// 128 bits set to zero.
if ('00000000-0000-0000-0000-000000000000' === $value) {
return;
}
if (!preg_match('/^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$/', $value)) {
throw new InvalidArgumentException(sprintf(
$message ?: 'Value "%s" is not a valid UUID.',
self::valueToString($value)
));
}
}
public static function __callStatic($name, $arguments)
{
if ('nullOr' === substr($name, 0, 6)) {
if (null !== $arguments[0]) {
$method = lcfirst(substr($name, 6));
call_user_func_array(array('static', $method), $arguments);
}
return;
}
if ('all' === substr($name, 0, 3)) {
self::isTraversable($arguments[0]);
$method = lcfirst(substr($name, 3));
$args = $arguments;
foreach ($arguments[0] as $entry) {
$args[0] = $entry;
call_user_func_array(array('static', $method), $args);
}
return;
}
throw new BadMethodCallException('No such method: '.$name);
}
protected static function valueToString($value)
{
if (null === $value) {
return 'null';
}
if (true === $value) {
return 'true';
}
if (false === $value) {
return 'false';
}
if (is_array($value)) {
return 'array';
}
if (is_object($value)) {
return get_class($value);
}
if (is_resource($value)) {
return 'resource';
}
if (is_string($value)) {
return '"'.$value.'"';
}
return (string) $value;
}
protected static function typeToString($value)
{
return is_object($value) ? get_class($value) : gettype($value);
}
protected static function strlen($value)
{
if (!function_exists('mb_detect_encoding')) {
return strlen($value);
}
if (false === $encoding = mb_detect_encoding($value)) {
return strlen($value);
}
return mb_strwidth($value, $encoding);
}
private function __construct()
{
}
}
#!/usr/bin/env php
<?php
/**
* if we're running from phar load the phar autoload,
* else let the script 'robo' search for the autoloader
*/
if (strpos(basename(__FILE__), 'phar')) {
require_once 'phar://robo.phar/vendor/autoload.php';
} else {
if (file_exists(__DIR__.'/vendor/autoload.php')) {
require_once __DIR__.'/vendor/autoload.php';
} elseif (file_exists(__DIR__.'/../../autoload.php')) {
require_once __DIR__ . '/../../autoload.php';
} else {
require_once 'phar://robo.phar/vendor/autoload.php';
}
}
$runner = new \Robo\Runner();
$statusCode = $runner->execute($_SERVER['argv']);
exit($statusCode);
q¤ÊX"zØœýf<C3BD>ê@Ø Ã‰²GBMB